/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ // One of the possible values for the mousewheel.* preferences. // From nsEventStateManager.cpp. const MOUSE_SCROLL_ZOOM = 3; /** * Controls the "full zoom" setting and its site-specific preferences. */ var FullZoom = FullZoom || { // Identifies the setting in the content prefs database. name: "browser.content.full-zoom", // The global value (if any) for the setting. Asynchronously loaded from the // service when first requested, then updated by the pref change listener as // it changes. If there is no global value, then this should be undefined. globalValue: undefined, // browser.zoom.siteSpecific preference cache _siteSpecificPref: undefined, // browser.zoom.updateBackgroundTabs preference cache updateBackgroundTabs: undefined, get siteSpecific() { return this._siteSpecificPref; }, //**************************************************************************// // nsISupports QueryInterface: XPCOMUtils.generateQI([Ci.nsIDOMEventListener, Ci.nsIObserver, Ci.nsIContentPrefObserver, Ci.nsIContentPrefCallback2, Ci.nsISupportsWeakReference]), //**************************************************************************// // Initialization & Destruction init: function FullZoom_init() { // Listen for scrollwheel events so we can save scrollwheel-based changes. window.addEventListener("wheel", this, true); // Fetch the initial global value. Services.contentPrefs2.getGlobal(this.name, null, this); // Register ourselves with the service so we know when our pref changes. Services.contentPrefs2.addObserverForName(this.name, this); this._siteSpecificPref = Services.prefs.getBoolPref("browser.zoom.siteSpecific"); this.updateBackgroundTabs = Services.prefs.getBoolPref("browser.zoom.updateBackgroundTabs"); // Listen for changes to the browser.zoom branch so we can enable/disable // updating background tabs and per-site saving and restoring of zoom levels. Services.prefs.addObserver("browser.zoom.", this, true); }, destroy: function FullZoom_destroy() { Services.prefs.removeObserver("browser.zoom.", this); Services.contentPrefs2.removeObserverForName(this.name, this); window.removeEventListener("wheel", this, true); }, //**************************************************************************// // Event Handlers // nsIDOMEventListener handleEvent: function FullZoom_handleEvent(event) { switch (event.type) { case "wheel": this._handleMouseScrolled(event); break; } }, _handleMouseScrolled: function FullZoom_handleMouseScrolled(event) { // Construct the "mousewheel action" pref key corresponding to this event. // Based on nsEventStateManager::WheelPrefs::GetIndexFor. var modifiers = { Alt: "mousewheel.with_alt.action", Control: "mousewheel.with_control.action", Meta: "mousewheel.with_meta.action", Shift: "mousewheel.with_shift.action", OS: "mousewheel.with_win.action" }; var pref = []; for (var key in modifiers) if (event.getModifierState(key)) pref.push(modifiers[key]); if (pref.length == 1) pref = pref[0]; else // Multiple or no modifiers, use default action pref = "mousewheel.default.action"; // Don't do anything if this isn't a "zoom" scroll event. if (Services.prefs.getIntPref(pref, 0) != MOUSE_SCROLL_ZOOM) 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 (self) { self._applySettingToPref() }, 0, this); }, // nsIObserver observe: function (aSubject, aTopic, aData) { switch (aTopic) { case "nsPref:changed": switch (aData) { case "browser.zoom.siteSpecific": this._siteSpecificPref = Services.prefs.getBoolPref("browser.zoom.siteSpecific"); break; case "browser.zoom.updateBackgroundTabs": this.updateBackgroundTabs = Services.prefs.getBoolPref("browser.zoom.updateBackgroundTabs"); break; } break; } }, // nsIContentPrefObserver onContentPrefSet: function FullZoom_onContentPrefSet(aGroup, aName, aValue) { if (aGroup == Services.contentPrefs2.extractDomain(getBrowser().currentURI.spec)) 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. var zoomValue = Services.contentPrefs2.getCachedByDomainAndName(getBrowser().currentURI.spec, this.name, getBrowser().docShell); if (zoomValue && !zoomValue.value) this._applyPrefToSetting(); } }, onContentPrefRemoved: function FullZoom_onContentPrefRemoved(aGroup, aName) { if (aGroup == Services.contentPrefs2.extractDomain(getBrowser().currentURI.spec)) 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. var zoomValue = Services.contentPrefs2.getCachedByDomainAndName(getBrowser().currentURI.spec, this.name, getBrowser().docShell); if (zoomValue && !zoomValue.value) this._applyPrefToSetting(); } }, // nsIContentPrefCallback2 handleCompletion: function(aReason) {}, handleError: function(aResult) {}, handleResult: function(aPref) { this.onContentPrefSet(null, this.name, aPref.value); }, // location change observer /** * Called when the location of a tab changes. * When that happens, we need to update the current zoom level if appropriate. * * @param aURI * A URI object representing the new location. * @param aIsTabSwitch * Whether this location change has happened because of a tab switch. * @param aBrowser * (optional) browser object displaying the document */ onLocationChange: function FullZoom_onLocationChange(aURI, aIsTabSwitch, aBrowser) { if (!aURI || !this.siteSpecific) return; // Avoid the cps roundtrip and apply the default/global pref. if (aURI.spec == "about:blank") { this._applyPrefToSetting(undefined, aBrowser); return; } // Image documents should always start at 1, and are not affected by prefs. if (!aIsTabSwitch && aBrowser.contentDocument.mozSyntheticDocument) { ZoomManager.setZoomForBrowser(aBrowser, this._ensureValid(1)); return; } var loadContext = aBrowser.docShell; var zoomValue = Services.contentPrefs2.getCachedByDomainAndName(aURI.spec, this.name, loadContext); if (zoomValue) { this._applyPrefToSetting(zoomValue.value, aBrowser); } else { Services.contentPrefs2.getByDomainAndName(aURI.spec, this.name, loadContext, { self: this, value: undefined, handleCompletion: function(aReason) { // Check that we're still where we expect to be in case this took a // while. Null check currentURI, since the window may have been // destroyed before we were called. if (aBrowser.currentURI && aURI.equals(aBrowser.currentURI)) this.self._applyPrefToSetting(this.value, aBrowser); }, handleError: function(aResult) {}, handleResult: function(aPref) { this.value = aPref.value; } }); } }, //**************************************************************************// // Setting & Pref Manipulation reduce: function FullZoom_reduce() { ZoomManager.reduce(); this._applySettingToPref(); }, enlarge: function FullZoom_enlarge() { ZoomManager.enlarge(); this._applySettingToPref(); }, zoom: function FullZoom_zoom(aZoomValue) { ZoomManager.zoom = aZoomValue; this._applySettingToPref(); }, reset: function FullZoom_reset() { if (typeof this.globalValue != "undefined") ZoomManager.zoom = this.globalValue; else ZoomManager.zoom = this._ensureValid(1); this._removePref(); }, setOther: function setZoomOther() { if (openZoomDialog()) this._applySettingToPref(); }, /** * Set the zoom level for the current tab. * * Per nsPresContext::setFullZoom, 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. In fact getting the zoom and then * checking ourselves if it differs costs more. * * And perhaps we should always set the zoom even if it was more expensive, * since DocumentViewerImpl::SetTextZoom claims that child documents can have * a different text zoom (although it would be unusual), and it implies that * those child text zooms should get updated when the parent zoom gets set, * and perhaps the same is true for full zoom * (although DocumentViewerImpl::SetFullZoom doesn't mention it). * * 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 FullZoom_applyPrefToSetting(aValue, aBrowser) { var browser = aBrowser || getBrowser(); if (!this.siteSpecific || window.gInPrintPreviewMode || browser.contentDocument.mozSyntheticDocument) return; try { if (typeof aValue != "undefined") ZoomManager.setZoomForBrowser(browser, this._ensureValid(aValue)); else if (typeof this.globalValue != "undefined") ZoomManager.setZoomForBrowser(browser, this.globalValue); else ZoomManager.setZoomForBrowser(browser, this._ensureValid(1)); } catch(ex) {} }, _applySettingToPref: function FullZoom_applySettingToPref() { if (!this.siteSpecific || window.gInPrintPreviewMode || content.document.mozSyntheticDocument) return; var zoomLevel = ZoomManager.zoom; Services.contentPrefs2.set(getBrowser().currentURI.spec, this.name, zoomLevel, getBrowser().docShell); }, _removePref: function FullZoom_removePref() { if (!content.document.mozSyntheticDocument) Services.contentPrefs2.removeByDomainAndName(getBrowser().currentURI.spec, this.name, getBrowser().docShell); }, //**************************************************************************// // Utilities _ensureValid: function FullZoom_ensureValid(aValue) { if (isNaN(aValue)) aValue = 1; if (aValue < ZoomManager.MIN) return ZoomManager.MIN; if (aValue > ZoomManager.MAX) return ZoomManager.MAX; return aValue; } }; /***** init and helper functions for viewZoomOverlay.xul *****/ window.addEventListener("load", registerZoomManager); window.addEventListener("unload", unregisterZoomManager); function registerZoomManager() { FullZoom.init(); var zoomBundle = document.getElementById("bundle_viewZoom"); var zoomMenu = document.getElementById("menu_zoom"); var parentMenu = zoomMenu.parentNode; parentMenu.addEventListener("popupshowing", updateViewMenu); // initialize menu from toolkit.zoomManager.zoomValues and assign accesskeys var zoomFactors = ZoomManager.zoomValues; var freeKeys = [ '0', '1', '2', '3', '4', '5', '6', '7', '8', '9' ]; var insertBefore = document.getElementById("menu_zoomInsertBefore"); var popup = insertBefore.parentNode; for (var i = 0; i < zoomFactors.length; ++i) { var thisFactor = Math.round(zoomFactors[i] * 100); var menuItem = document.createElement("menuitem"); menuItem.setAttribute("type", "radio"); menuItem.setAttribute("name", "zoom"); var label; var accessKey = ""; if (thisFactor == 100) { label = zoomBundle.getString("zoom.100.label"); accessKey = zoomBundle.getString("zoom.100.accesskey"); menuItem.setAttribute("key", "key_zoomReset"); } else if (thisFactor == 200) { label = zoomBundle.getString("zoom.200.label"); accessKey = zoomBundle.getString("zoom.200.accesskey"); } else if (thisFactor == Math.round(ZoomManager.MIN * 100)) { label = zoomBundle.getString("zoom.min.label") .replace(/%zoom%/, thisFactor); accessKey = zoomBundle.getString("zoom.min.accesskey"); } else if (thisFactor == Math.round(ZoomManager.MAX * 100)) { label = zoomBundle.getString("zoom.max.label") .replace(/%zoom%/, thisFactor); accessKey = zoomBundle.getString("zoom.max.accesskey"); } else { label = zoomBundle.getString("zoom.value.label") .replace(/%zoom%/, thisFactor); for (var j = 0; j < label.length; ++j) { var testKey = label[j]; var indexKey = freeKeys.indexOf(testKey); if (indexKey >= 0) { accessKey = testKey; freeKeys.splice(indexKey, 1); break; } } } menuItem.setAttribute("label", label); if (accessKey) menuItem.setAttribute("accesskey", accessKey); menuItem.setAttribute("value", thisFactor); popup.insertBefore(menuItem, insertBefore); } } function unregisterZoomManager() { FullZoom.destroy(); } function updateViewMenu() { var zoomBundle = document.getElementById("bundle_viewZoom"); var zoomMenu = document.getElementById("menu_zoom"); var zoomType = ZoomManager.useFullZoom ? "fullZoom" : "textZoom"; var menuLabel = zoomBundle.getString(zoomType + ".label") .replace(/%zoom%/, Math.round(ZoomManager.zoom * 100)); var menuKey = zoomBundle.getString(zoomType + ".accesskey"); zoomMenu.setAttribute("label", menuLabel); zoomMenu.setAttribute("accesskey", menuKey); } function updateZoomMenu() { var zoomBundle = document.getElementById("bundle_viewZoom"); var zoomOther = document.getElementById("menu_zoomOther"); var label = zoomBundle.getString("zoom.other.label"); var accesskey = zoomBundle.getString("zoom.other.accesskey"); var factorOther = zoomOther.getAttribute("value") || Math.round(ZoomManager.MAX * 100); zoomOther.setAttribute("label", label.replace(/%zoom%/, factorOther)); zoomOther.setAttribute("accesskey", accesskey); zoomOther.setAttribute("value", factorOther); var popup = document.getElementById("menu_zoomPopup"); var item = popup.lastChild; while (item) { if (item.getAttribute("name") == "zoom") { if (item.getAttribute("value") == Math.round(ZoomManager.zoom * 100)) item.setAttribute("checked","true"); else item.removeAttribute("checked"); } item = item.previousSibling; } } function openZoomDialog() { var zoomOther = document.getElementById("menu_zoomOther"); // open dialog and ask for new value var o = {value: zoomOther.getAttribute("value"), zoomMin: ZoomManager.MIN * 100, zoomMax: ZoomManager.MAX * 100}; window.openDialog("chrome://communicator/content/askViewZoom.xul", "", "chrome,modal,centerscreen", o); if (o.zoomOK) { zoomOther.setAttribute("value", o.value); ZoomManager.zoom = o.value / 100; } return o.zoomOK; } function zoomEnlarge() { FullZoom.enlarge(); updateZoomStatus(); } function zoomReduce() { FullZoom.reduce(); updateZoomStatus(); } function zoomReset() { FullZoom.reset(); updateZoomStatus(); } function zoomSetOther() { FullZoom.setOther(); updateZoomStatus(); } function zoomToggle() { ZoomManager.toggleZoom(); updateZoomStatus(); } function zoomSet(aValue) { FullZoom.zoom(aValue) updateZoomStatus(); }