summaryrefslogtreecommitdiffstats
path: root/browser/modules/ZoomUI.jsm
diff options
context:
space:
mode:
Diffstat (limited to 'browser/modules/ZoomUI.jsm')
-rw-r--r--browser/modules/ZoomUI.jsm213
1 files changed, 213 insertions, 0 deletions
diff --git a/browser/modules/ZoomUI.jsm b/browser/modules/ZoomUI.jsm
new file mode 100644
index 0000000000..343f8c16d2
--- /dev/null
+++ b/browser/modules/ZoomUI.jsm
@@ -0,0 +1,213 @@
+// -*- indent-tabs-mode: nil; js-indent-level: 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/. */
+
+"use strict";
+
+var EXPORTED_SYMBOLS = ["ZoomUI"];
+const gLoadContext = Cu.createLoadContext();
+const gContentPrefs = Cc["@mozilla.org/content-pref/service;1"].getService(
+ Ci.nsIContentPrefService2
+);
+const gZoomPropertyName = "browser.content.full-zoom";
+
+const lazy = {};
+
+ChromeUtils.defineESModuleGetters(lazy, {
+ PanelMultiView: "resource:///modules/PanelMultiView.sys.mjs",
+});
+
+var ZoomUI = {
+ init(aWindow) {
+ aWindow.addEventListener("EndSwapDocShells", onEndSwapDocShells, true);
+ aWindow.addEventListener("FullZoomChange", onZoomChange);
+ aWindow.addEventListener("TextZoomChange", onZoomChange);
+ aWindow.addEventListener(
+ "unload",
+ () => {
+ aWindow.removeEventListener(
+ "EndSwapDocShells",
+ onEndSwapDocShells,
+ true
+ );
+ aWindow.removeEventListener("FullZoomChange", onZoomChange);
+ aWindow.removeEventListener("TextZoomChange", onZoomChange);
+ },
+ { once: true }
+ );
+ },
+
+ /**
+ * Gets the global browser.content.full-zoom content preference.
+ *
+ * @returns Promise<prefValue>
+ * Resolves to the preference value (float) when done.
+ */
+ getGlobalValue() {
+ return new Promise(resolve => {
+ let cachedVal = gContentPrefs.getCachedGlobal(
+ gZoomPropertyName,
+ gLoadContext
+ );
+ if (cachedVal) {
+ // We've got cached information, though it may be we've cached
+ // an undefined value, or the cached info is invalid. To ensure
+ // a valid return, we opt to return the default 1.0 in the
+ // undefined and invalid cases.
+ resolve(parseFloat(cachedVal.value) || 1.0);
+ return;
+ }
+ // Otherwise, nothing is cached, so we must do a full lookup
+ // with `gContentPrefs.getGlobal()`.
+ let value = 1.0;
+ gContentPrefs.getGlobal(gZoomPropertyName, gLoadContext, {
+ handleResult(pref) {
+ if (pref.value) {
+ value = parseFloat(pref.value);
+ }
+ },
+ handleCompletion(reason) {
+ resolve(value);
+ },
+ handleError(error) {
+ console.error(error);
+ },
+ });
+ });
+ },
+};
+
+function fullZoomLocationChangeObserver(aSubject, aTopic) {
+ // If the tab was the last one in its window and has been dragged to another
+ // window, the original browser's window will be unavailable here. Since that
+ // window is closing, we can just ignore this notification.
+ if (!aSubject.ownerGlobal) {
+ return;
+ }
+ updateZoomUI(aSubject, false);
+}
+Services.obs.addObserver(
+ fullZoomLocationChangeObserver,
+ "browser-fullZoom:location-change"
+);
+
+function onEndSwapDocShells(event) {
+ updateZoomUI(event.originalTarget);
+}
+
+function onZoomChange(event) {
+ let browser;
+ if (event.target.nodeType == event.target.DOCUMENT_NODE) {
+ // In non-e10s, the event is dispatched on the contentDocument
+ // so we need to jump through some hoops to get to the <xul:browser>.
+ let topDoc = event.target.defaultView.top.document;
+ if (!topDoc.documentElement) {
+ // In some events, such as loading synthetic documents, the
+ // documentElement will be null and we won't be able to find
+ // an associated browser.
+ return;
+ }
+ browser = topDoc.ownerGlobal.docShell.chromeEventHandler;
+ } else {
+ browser = event.originalTarget;
+ }
+ updateZoomUI(browser, true);
+}
+
+/**
+ * Updates zoom controls.
+ *
+ * @param {object} aBrowser The browser that the zoomed content resides in.
+ * @param {boolean} aAnimate Should be True for all cases unless the zoom
+ * change is related to tab switching. Optional
+ */
+async function updateZoomUI(aBrowser, aAnimate = false) {
+ let win = aBrowser.ownerGlobal;
+ if (!win.gBrowser || win.gBrowser.selectedBrowser != aBrowser) {
+ return;
+ }
+
+ let appMenuZoomReset = lazy.PanelMultiView.getViewNode(
+ win.document,
+ "appMenu-zoomReset-button2"
+ );
+
+ // Exit early if UI elements aren't present.
+ if (!appMenuZoomReset) {
+ return;
+ }
+
+ let customizableZoomControls = win.document.getElementById("zoom-controls");
+ let customizableZoomReset = win.document.getElementById("zoom-reset-button");
+ let urlbarZoomButton = win.document.getElementById("urlbar-zoom-button");
+ let zoomFactor = Math.round(win.ZoomManager.zoom * 100);
+
+ let defaultZoom = Math.round((await ZoomUI.getGlobalValue()) * 100);
+
+ if (!win.gBrowser || win.gBrowser.selectedBrowser != aBrowser) {
+ // Because the CPS call is async, at this point the selected browser
+ // may have changed. We should re-check whether the browser for which we've
+ // been notified is still the selected browser and bail out if not.
+ // If the selected browser changed (again), we will have been called again
+ // with the "right" browser, and that'll update the zoom level.
+ return;
+ }
+
+ // Hide urlbar zoom button if zoom is at the default zoom level,
+ // if we're viewing an about:blank page with an empty/null
+ // principal, if the PDF viewer is currently open,
+ // or if the customizable control is in the toolbar.
+
+ urlbarZoomButton.hidden =
+ defaultZoom == zoomFactor ||
+ (aBrowser.currentURI.spec == "about:blank" &&
+ (!aBrowser.contentPrincipal ||
+ aBrowser.contentPrincipal.isNullPrincipal)) ||
+ (aBrowser.contentPrincipal &&
+ aBrowser.contentPrincipal.spec == "resource://pdf.js/web/viewer.html") ||
+ (customizableZoomControls &&
+ customizableZoomControls.getAttribute("cui-areatype") == "toolbar");
+
+ let label = win.gNavigatorBundle.getFormattedString("zoom-button.label", [
+ zoomFactor,
+ ]);
+ if (appMenuZoomReset) {
+ appMenuZoomReset.setAttribute("label", label);
+ }
+ if (customizableZoomReset) {
+ customizableZoomReset.setAttribute("label", label);
+ }
+ if (!urlbarZoomButton.hidden) {
+ if (aAnimate && !win.gReduceMotion) {
+ urlbarZoomButton.setAttribute("animate", "true");
+ } else {
+ urlbarZoomButton.removeAttribute("animate");
+ }
+ urlbarZoomButton.setAttribute("label", label);
+ }
+
+ win.FullZoom.updateCommands();
+}
+
+const { CustomizableUI } = ChromeUtils.importESModule(
+ "resource:///modules/CustomizableUI.sys.mjs"
+);
+let customizationListener = {};
+customizationListener.onWidgetAdded =
+ customizationListener.onWidgetRemoved =
+ customizationListener.onWidgetMoved =
+ function (aWidgetId) {
+ if (aWidgetId == "zoom-controls") {
+ for (let window of CustomizableUI.windows) {
+ updateZoomUI(window.gBrowser.selectedBrowser);
+ }
+ }
+ };
+customizationListener.onWidgetReset = customizationListener.onWidgetUndoMove =
+ function (aWidgetNode) {
+ if (aWidgetNode.id == "zoom-controls") {
+ updateZoomUI(aWidgetNode.ownerGlobal.gBrowser.selectedBrowser);
+ }
+ };
+CustomizableUI.addListener(customizationListener);