diff options
Diffstat (limited to 'comm/suite/base/content/viewZoomOverlay.js')
-rw-r--r-- | comm/suite/base/content/viewZoomOverlay.js | 479 |
1 files changed, 479 insertions, 0 deletions
diff --git a/comm/suite/base/content/viewZoomOverlay.js b/comm/suite/base/content/viewZoomOverlay.js new file mode 100644 index 0000000000..d14ef9d67d --- /dev/null +++ b/comm/suite/base/content/viewZoomOverlay.js @@ -0,0 +1,479 @@ +/* -*- 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(); +} |