diff --git a/browser/base/content/browser-contentPrefSink.js b/browser/base/content/browser-contentPrefSink.js new file mode 100755 index 000000000000..81547ae331e7 --- /dev/null +++ b/browser/base/content/browser-contentPrefSink.js @@ -0,0 +1,256 @@ +/* +#ifdef 0 + * ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Content Preferences (cpref). + * + * The Initial Developer of the Original Code is Mozilla. + * Portions created by the Initial Developer are Copyright (C) 2006 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Myk Melez + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** +#endif + */ + +/** + * Listens for site-specific pref-related events and dispatches them + * to setting controllers. + */ + +var ContentPrefSink = { + //**************************************************************************// + // Convenience Getters + + // Content Pref Service + __cps: null, + get _cps() { + if (!this.__cps) + this.__cps = Cc["@mozilla.org/content-pref/service;1"]. + getService(Ci.nsIContentPrefService); + return this.__cps; + }, + + + //**************************************************************************// + // nsISupports + + // Note: we can't use the Ci shortcut here because it isn't defined yet. + interfaces: [Components.interfaces.nsIDOMEventListener, + Components.interfaces.nsISupports], + + QueryInterface: function ContentPrefSink_QueryInterface(iid) { + if (!this.interfaces.some( function(v) { return iid.equals(v) } )) + throw Cr.NS_ERROR_NO_INTERFACE; + return this; + }, + + + //**************************************************************************// + // Initialization & Destruction + + init: function ContentPrefSink_init() { + gBrowser.addProgressListener(this, Ci.nsIWebProgress.NOTIFY_STATE_DOCUMENT); + gBrowser.addEventListener("DOMContentLoaded", this, false); + // XXX Should we also listen for pageshow and/or load? + }, + + destroy: function ContentPrefSink_destroy() { + gBrowser.removeEventListener("DOMContentLoaded", this, false); + gBrowser.removeProgressListener(this); + + this._observers = null; + }, + + + //**************************************************************************// + // Event Handlers + + // nsIWebProgressListener + + onLocationChange: function ContentPrefSink_onLocationChange(progress, request, uri) { + // Catch exceptions until bug 376222 gets fixed so that we don't hork + // other progress listeners if our code throws an exception. + try { + this._handleLocationChanged(uri); + } + catch(ex) { + Components.utils.reportError(ex); + } + }, + onStateChange: function ContentPrefSink_onStateChange(progress, request, flags, status) {}, + onProgressChange: function ContentPrefSink_onProgressChange(progress, request, curSelfProgress, + maxSelfProgress, curTotalProgress, + maxTotalProgress) {}, + onStatusChange: function ContentPrefSink_onStatusChange(progress, request, status, message) {}, + onSecurityChange: function ContentPrefSink_onSecurityChange(progress, request, state) {}, + + // nsIDOMEventListener + + handleEvent: function ContentPrefSink_handleEvent(event) { + // The only events we handle are DOMContentLoaded events. + this._handleDOMContentLoaded(event); + }, + + _handleLocationChanged: function ContentPrefSink__handleLocationChanged(uri) { + if (!uri) + return; + + var prefs = this._cps.getPrefs(uri); + + for (var name in this._observers) { + var value = prefs.hasKey(name) ? prefs.get(name) : undefined; + for each (var observer in this._observers[name]) + if (observer.onLocationChanged) { + try { + observer.onLocationChanged(uri, name, value); + } + catch(ex) { + Components.utils.reportError(ex); + } + } + } + + for each (var observer in this._genericObservers) + if (observer.onLocationChanged) { + try { + observer.onLocationChanged(uri, prefs); + } + catch(ex) { + Components.utils.reportError(ex); + } + } + }, + + _handleDOMContentLoaded: function ContentPrefSink__handleDOMContentLoaded(event) { + var browser = gBrowser.getBrowserForDocument(event.target); + // If the document isn't one of the ones loaded into a top-level browser, + // don't notify observers about it. XXX Might some observers want to know + // about documents loaded into iframes? + if (!browser) + return; + var uri = browser.currentURI; + + var prefs = this._cps.getPrefs(uri); + + for (var name in this._observers) { + var value = prefs.hasKey(name) ? prefs.get(name) : undefined; + for each (var observer in this._observers[name]) { + if (observer.onDOMContentLoaded) { + try { + observer.onDOMContentLoaded(event, name, value); + } + catch(ex) { + Components.utils.reportError(ex); + } + } + } + } + + for each (var observer in this._genericObservers) { + if (observer.onDOMContentLoaded) { + try { + observer.onDOMContentLoaded(event, prefs); + } + catch(ex) { + Components.utils.reportError(ex); + } + } + } + }, + + + //**************************************************************************// + // Sink Observers + + _observers: {}, + _genericObservers: [], + + // Note: this isn't an XPCOM interface (for performance and so we can pass + // nsIVariants), although it resembles nsIObserverService. Sink observers + // are regular JS objects, and they get called directly, not via XPConnect. + + /** + * Add an observer. + * + * The observer can be setting-specific or generic, which affects what it + * gets handed when the sink notifies it about an event. A setting-specific + * observer gets handed the value of its pref for the target page, while + * a generic observer gets handed the values of all prefs for the target page. + * + * @param name the name of the setting for which to add an observer, + * or null to add a generic observer + * @param observer the observer to add + * + * @returns if setting-specific, the global preference for the setting + * if generic, null + */ + addObserver: function ContentPrefSink_addObserver(name, observer) { + var observers; + if (name) { + if (!this._observers[name]) + this._observers[name] = []; + observers = this._observers[name]; + } + else + observers = this._genericObservers; + + if (observers.indexOf(observer) == -1) + observers.push(observer); + + return name ? this._cps.getPref(null, name) : null; + }, + + /** + * Remove an observer. + * + * @param name the name of the setting for which to remove + * an observer, or null to remove a generic observer + * @param observer the observer to remove + */ + removeObserver: function ContentPrefSink_removeObserver(name, observer) { + var observers = name ? this._observers[name] : this._genericObservers; + if (observers.indexOf(observer) != -1) + observers.splice(observers.indexOf(observer), 1); + }, + + _getObservers: function ContentPrefSink__getObservers(name) { + var observers = []; + + // Construct the list of observers, putting setting-specific ones before + // generic ones, so observers that initialize individual settings (like + // the page style controller) execute before observers that do something + // with multiple settings and depend on them being initialized first + // (f.e. the content prefs sidebar). + if (name && this._observers[name]) + observers = observers.concat(this._observers[name]); + observers = observers.concat(this._genericObservers); + + return observers; + } +}; diff --git a/browser/base/content/browser-sets.inc b/browser/base/content/browser-sets.inc index 1c07522a25e8..6f6afb176f47 100644 --- a/browser/base/content/browser-sets.inc +++ b/browser/base/content/browser-sets.inc @@ -120,9 +120,9 @@ - - - + + + diff --git a/browser/base/content/browser-textZoom.js b/browser/base/content/browser-textZoom.js new file mode 100755 index 000000000000..e1502774158f --- /dev/null +++ b/browser/base/content/browser-textZoom.js @@ -0,0 +1,323 @@ +/* +#ifdef 0 + * ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Content Preferences (cpref). + * + * The Initial Developer of the Original Code is Mozilla. + * Portions created by the Initial Developer are Copyright (C) 2006 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Myk Melez + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** +#endif + */ + +// From nsMouseScrollEvent::kIsHorizontal +const MOUSE_SCROLL_IS_HORIZONTAL = 1 << 2; + +// Not sure where this comes from. It's one of the possible values +// for the mousewheel.* preferences. +const MOUSE_SCROLL_TEXTSIZE = 3; + +/** + * Controls the "text zoom" setting and its site-specific preferences. + */ +var TextZoom = { + + //**************************************************************************// + // Name & Values + + // The name of the setting. Identifies the setting in the prefs database. + name: "browser.content.text-zoom", + + // The global value (if any) for the setting. Retrieved from the prefs + // database when this handler gets initialized, then updated as it changes. + // If there is no global value, then this should be undefined. + globalValue: undefined, + + // From viewZoomOverlay.js + minValue: 1, + maxValue: 2000, + defaultValue: 100, + + + //**************************************************************************// + // Convenience Getters + + __zoomManager: null, + get _zoomManager() { + if (!this.__zoomManager) + this.__zoomManager = ZoomManager.prototype.getInstance(); + return this.__zoomManager; + }, + + // Content Pref Service + __cps: null, + get _cps() { + if (!this.__cps) + this.__cps = Cc["@mozilla.org/content-pref/service;1"]. + getService(Ci.nsIContentPrefService); + return this.__cps; + }, + + // Pref Branch + __prefBranch: null, + get _prefBranch() { + if (!this.__prefBranch) + this.__prefBranch = Cc["@mozilla.org/preferences-service;1"]. + getService(Ci.nsIPrefBranch); + return this.__prefBranch; + }, + + + //**************************************************************************// + // nsISupports + + // We can't use the Ci shortcut here because it isn't defined yet. + interfaces: [Components.interfaces.nsIDOMEventListener, + Components.interfaces.nsIContentPrefObserver, + Components.interfaces.nsISupports], + + QueryInterface: function TextZoom_QueryInterface(aIID) { + if (!this.interfaces.some( function(v) { return aIID.equals(v) } )) + throw Cr.NS_ERROR_NO_INTERFACE; + return this; + }, + + + //**************************************************************************// + // Initialization & Destruction + + init: function TextZoom_init() { + // Listen for scrollwheel events so we can save scrollwheel-based changes. + window.addEventListener("DOMMouseScroll", this, false); + + // Register ourselves with the service so we know when our pref changes. + this._cps.addObserver(this.name, this); + + // Register ourselves with the sink so we know when the location changes. + var globalValue = ContentPrefSink.addObserver(this.name, this); + this.globalValue = this._ensureValid(globalValue); + + // Set the initial value of the setting. + this._applyPrefToSetting(); + }, + + destroy: function TextZoom_destroy() { + ContentPrefSink.removeObserver(this.name, this); + this._cps.removeObserver(this.name, this); + window.removeEventListener("DOMMouseScroll", this, false); + }, + + + //**************************************************************************// + // Event Handlers + + // nsIDOMEventListener + + handleEvent: function TextZoom_handleEvent(event) { + // The only events we handle are DOMMouseScroll events. + this._handleMouseScrolled(event); + }, + + _handleMouseScrolled: function TextZoom__handleMouseScrolled(event) { + // Construct the "mousewheel action" pref key corresponding to this event. + // Based on nsEventStateManager::GetBasePrefKeyForMouseWheel. + var pref = "mousewheel"; + if (event.scrollFlags & MOUSE_SCROLL_IS_HORIZONTAL) + pref += ".horizscroll"; + + if (event.shiftKey) + pref += ".withshiftkey"; + else if (event.ctrlKey) + pref += ".withcontrolkey"; + else if (event.altKey) + pref += ".withaltkey"; + else if (event.metaKey) + pref += ".withmetakey"; + else + pref += ".withnokey"; + + pref += ".action"; + + // Don't do anything if this isn't a "change text size" scroll event. + if (this._getAppPref(pref, null) != MOUSE_SCROLL_TEXTSIZE) + return; + + // XXX Lazily cache all the possible action prefs so we don't have to get + // them anew from the pref service for every scroll event? We'd have to + // make sure to observe them so we can update the cache when they change. + + // We have to call _applySettingToPref in a timeout because we handle + // the event before the event state manager has a chance to apply the zoom + // during nsEventStateManager::PostHandleEvent. + window.setTimeout(function() { TextZoom._applySettingToPref() }, 0); + }, + + // nsIContentPrefObserver + + onContentPrefSet: function TextZoom_onContentPrefSet(aGroup, aName, aValue) { + if (aGroup == this._cps.grouper.group(gBrowser.currentURI)) + this._applyPrefToSetting(aValue); + else if (aGroup == null) { + this.globalValue = this._ensureValid(aValue); + + // If the current page doesn't have a site-specific preference, + // then its zoom should be set to the new global preference now that + // the global preference has changed. + if (!this._cps.hasPref(gBrowser.currentURI, this.name)) + this._applyPrefToSetting(); + } + }, + + onContentPrefRemoved: function TextZoom_onContentPrefRemoved(aGroup, aName) { + if (aGroup == this._cps.grouper.group(gBrowser.currentURI)) + this._applyPrefToSetting(); + else if (aGroup == null) { + this.globalValue = undefined; + + // If the current page doesn't have a site-specific preference, + // then its zoom should be set to the default preference now that + // the global preference has changed. + if (!this._cps.hasPref(gBrowser.currentURI, this.name)) + this._applyPrefToSetting(); + } + }, + + // ContentPrefSink observer + + onLocationChanged: function TextZoom_onLocationChanged(aURI, aName, aValue) { + this._applyPrefToSetting(aValue); + }, + + + //**************************************************************************// + // Setting & Pref Manipulation + + reduce: function TextZoom_reduce() { + this._zoomManager.reduce(); + this._applySettingToPref(); + }, + + enlarge: function TextZoom_enlarge() { + this._zoomManager.enlarge(); + this._applySettingToPref(); + }, + + reset: function TextZoom_reset() { + if (typeof this.globalValue != "undefined") + this._zoomManager.textZoom = this.globalValue; + else + this._zoomManager.reset(); + + this._removePref(); + }, + + /** + * Set the text zoom for the current tab. + * + * Per DocumentViewerImpl::SetTextZoom in nsDocumentViewer.cpp, it looks + * like we can set the zoom to its current value without significant impact + * on performance, as the setting is only applied if it differs from the + * current setting. + * + * And perhaps we should always set the zoom even if it were to incur + * a performance penalty, since SetTextZoom claims that child documents + * may have a different zoom under unusual circumstances, and it implies + * that those child zooms should get updated when the parent zoom gets set. + * + * So when we apply new zoom values to the browser, we simply set the zoom. + * We don't check first to see if the new value is the same as the current + * one. + **/ + _applyPrefToSetting: function TextZoom__applyPrefToSetting(aValue) { + // Bug 375918 means this will sometimes throw, so we catch it + // and don't do anything in those cases. + try { + if (typeof aValue != "undefined") + this._zoomManager.textZoom = this._ensureValid(aValue); + else if (typeof this.globalValue != "undefined") + this._zoomManager.textZoom = this.globalValue; + else + this._zoomManager.reset(); + } + catch(ex) {} + }, + + _applySettingToPref: function TextZoom__applySettingToPref() { + var textZoom = this._zoomManager.textZoom; + this._cps.setPref(gBrowser.currentURI, this.name, textZoom); + }, + + _removePref: function TextZoom__removePref() { + this._cps.removePref(gBrowser.currentURI, this.name); + }, + + + //**************************************************************************// + // Utilities + + _ensureValid: function TextZoom__ensureValid(aValue) { + if (isNaN(aValue)) + return this.defaultValue; + + if (aValue < this.minValue) + return this.minValue; + + if (aValue > this.maxValue) + return this.maxValue; + + return aValue; + }, + + /** + * Get a value from a pref or a default value if the pref doesn't exist. + * + * @param aPrefName + * @param aDefaultValue + * @returns the pref's value or the default (if it is missing) + */ + _getAppPref: function TextZoom__getAppPref(aPrefName, aDefaultValue) { + try { + switch (this._prefBranch.getPrefType(aPrefName)) { + case this._prefBranch.PREF_STRING: + return this._prefBranch.getCharPref(aPrefName); + + case this._prefBranch.PREF_BOOL: + return this._prefBranch.getBoolPref(aPrefName); + + case this._prefBranch.PREF_INT: + return this._prefBranch.getIntPref(aPrefName); + } + } + catch (ex) { /* return the default value */ } + + return aDefaultValue; + } +}; diff --git a/browser/base/content/browser.js b/browser/base/content/browser.js index c24e077a9174..d1c21ec461d2 100644 --- a/browser/base/content/browser.js +++ b/browser/base/content/browser.js @@ -1146,6 +1146,17 @@ function delayedStartup() // to create its singleton, whose constructor initializes the service. Cc["@mozilla.org/microsummary/service;1"].getService(Ci.nsIMicrosummaryService); + // Initialize the content pref event sink and the text zoom setting. + // We do this before the session restore service gets initialized so we can + // apply text zoom settings to tabs restored by the session restore service. + try { + ContentPrefSink.init(); + TextZoom.init(); + } + catch(ex) { + Components.utils.reportError(ex); + } + // initialize the session-restore service (in case it's not already running) if (document.documentElement.getAttribute("windowtype") == "navigator:browser") { try { @@ -1170,6 +1181,14 @@ function delayedStartup() function BrowserShutdown() { + try { + TextZoom.destroy(); + ContentPrefSink.destroy(); + } + catch(ex) { + Components.utils.reportError(ex); + } + var os = Components.classes["@mozilla.org/observer-service;1"] .getService(Components.interfaces.nsIObserverService); os.removeObserver(gSessionHistoryObserver, "browser:purge-session-history"); @@ -5669,6 +5688,9 @@ var FeedHandler = { #include browser-places.js #endif +#include browser-contentPrefSink.js +#include browser-textZoom.js + /** * This object is for augmenting tabs */