/* #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); // Delete references to XPCOM components to make sure we don't leak them // (although we haven't observed leakage in tests). Also delete references // in _observers and _genericObservers to avoid cycles with those that // refer to us and don't remove themselves from those observer pools. for (var i in this) { try { this[i] = null } // Ignore "setting a property that has only a getter" exceptions. catch(ex) {} } }, //**************************************************************************// // 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; } };