commit 89a15ab99da7cc73074f4e5c53260a688f15ffe9
Author: Arthur Edelstein <arthuredelstein(a)gmail.com>
Date: Wed Apr 1 17:57:29 2015 +0100
Bug #14429, Part 3. Improve behavior of content window resizer
---
src/chrome/content/content-sizer.js | 503 +++++++++++++++++++++--------
src/chrome/content/torbutton.js | 35 +-
src/chrome/locale/en/torbutton.properties | 1 +
src/modules/utils.js | 15 +-
4 files changed, 395 insertions(+), 159 deletions(-)
diff --git a/src/chrome/content/content-sizer.js b/src/chrome/content/content-sizer.js
index f3710f7..e627c3c 100644
--- a/src/chrome/content/content-sizer.js
+++ b/src/chrome/content/content-sizer.js
@@ -3,28 +3,105 @@
// This file is formatted for docco.js. Later functions call earlier ones.
+/*
+TODO:
+* Decide on quantization amount. 100x100? 200x100? Maybe gradually increase, like 50, 100, 150, 200, 300, 500, 600, 800, etc.?
+* Understand gBrowser.contentWindow.document.body.getBoundingClientRect(). Does this leak some fingerprintable information?
+* Modify Tor Browser C++ code to allow precise setting of zoom? (Would allow more precise fit of content height in window.)
+*/
+
/* jshint esnext: true */
// __quantizeBrowserSize(window, xStep, yStep)__.
// Ensures that gBrowser width and height are multiples of
// xStep and yStep.
let quantizeBrowserSize = function (window, xStep, yStep) {
+"use strict";
+
+// __currentDefaultZoom__.
+// The settings of gBrowser.fullZoom used to quantize the content window dimensions,
+// except if the user has pressed zoom+ or zoom-. Stateful.
+let currentDefaultZoom = 1;
+
+// ## Utilities
+
+// Mozilla abbreviations.
+let {classes: Cc, interfaces: Ci, results: Cr, Constructor: CC, utils: Cu } = Components;
// Use Task.jsm to avoid callback hell.
Cu.import("resource://gre/modules/Task.jsm");
// Make the TorButton logger available.
let logger = Cc["@torproject.org/torbutton-logger;1"]
- .getService(Components.interfaces.nsISupports).wrappedJSObject;
+ .getService(Ci.nsISupports).wrappedJSObject;
+
+// __torbuttonBundle__.
+// Bundle of localized strings for torbutton UI.
+let torbuttonBundle = Services.strings.createBundle(
+ "chrome://torbutton/locale/torbutton.properties");
+
+// Import utility functions
+let { bindPrefAndInit, getEnv } = Cu.import("resource://torbutton/modules/utils.js");
+
+// __windowUtils(window)__.
+// See nsIDOMWindowUtils on MDN.
+let windowUtils = window => window.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIDOMWindowUtils);
+
+// __isNumber(value)__.
+// Returns true iff the value is a number.
+let isNumber = x => typeof x === "number";
+
+// __sortBy(array, scoreFn)__.
+// Returns a copy of the array, sorted from least to best
+// according to scoreFn.
+let sortBy = function (array, scoreFn) {
+ let compareFn = (a, b) => scoreFn(a) - scoreFn(b);
+ return array.slice().sort(compareFn);
+};
-// Utility function
-let { bindPrefAndInit } = Cu.import("resource://torbutton/modules/utils.js");
+// __isMac__.
+// Constant, set to true if we are using a Mac (Darwin).
+let isMac = Services.appinfo.OS === "Darwin";
+
+// __isWindows__.
+// Constant, set to true if we are using Windows.
+let isWindows = Services.appinfo.OS === "WINNT";
+
+// __isTilingWindowManager__.
+// Constant, set to true if we are using a (known) tiling window
+// manager in linux.
+let isTilingWindowManager = (function () {
+ if (isMac || isWindows) return false;
+ let gdmSession = getEnv("GDMSESSION");
+ if (!gdmSession) return false;
+ let gdmSessionLower = gdmSession.toLowerCase();
+ return ["9wm", "alopex", "awesome", "bspwm", "catwm", "dswm", "dwm",
+ "echinus", "euclid-wm", "frankenwm", "herbstluftwm", "i3",
+ "i3wm", "ion", "larswm", "monsterwm", "musca", "notion",
+ "qtile", "ratpoison", "snapwm", "spectrwm", "stumpwm",
+ "subtle", "tinywm", "ttwm", "wingo", "wmfs", "wmii", "xmonad"]
+ .filter(x => x.startsWith(gdmSessionLower)).length > 0;
+})();
// __largestMultipleLessThan(factor, max)__.
// Returns the largest number that is a multiple of factor
// and is less or equal to max.
let largestMultipleLessThan = function (factor, max) {
- return Math.max(1, Math.floor((1 + max) / factor, 1)) * factor;
+ return Math.max(1, Math.floor(max / factor, 1)) * factor;
+};
+
+// ## Task.jsm helper functions
+
+// __sleep(timeMs)__.
+// Returns a Promise that sleeps for the specified time interval,
+// and returns an Event object of type "wake".
+let sleep = function (timeMs) {
+ return new Promise(function (resolve, reject) {
+ window.setTimeout(function () {
+ resolve(new Event("wake"));
+ }, timeMs);
+ });
};
// __listen(target, eventType, useCapture, timeoutMs)__.
@@ -47,20 +124,201 @@ let listen = function (target, eventType, useCapture, timeoutMs) {
});
};
-// __sleep(time_ms)__.
-// Returns a Promise that sleeps for the specified time interval,
-// and returns an Event object of type "wake".
-let sleep = function (timeoutMs) {
- return new Promise(function (resolve, reject) {
- window.setTimeout(function () {
- resolve(new Event("wake"));
- }, timeoutMs);
- });
+// __listenForTrueResize(window, timeoutMs)__.
+// Task.jsm function. Call `yield listenForTrueResize(window)` to
+// wait until the window changes its outer dimensions. Ignores
+// resize events where window dimensions are unchanged. Returns
+// the resize event object.
+let listenForTrueResize = function* (window, timeoutMs) {
+ let [originalWidth, originalHeight] = [window.outerWidth, window.outerHeight],
+ event,
+ finishTime = timeoutMs ? Date.now() + timeoutMs : null;
+ do {
+ event = yield listen(window, "resize", true,
+ finishTime ? finishTime - Date.now() : undefined);
+ } while (event.type === "resize" &&
+ originalWidth === window.outerWidth &&
+ originalHeight === window.outerHeight);
+ return event;
};
-// __isNumber(value)__.
-// Returns true iff the value is a number.
-let isNumber = x => typeof x === "number";
+// ## Window state queries
+
+// __trueZoom(window)__.
+// Returns the true magnification of the content in the window
+// object. (In contrast, the `gBrowser.fullZoom` value is only approximated
+// by the display zoom.)
+let trueZoom = window => windowUtils(window).screenPixelsPerCSSPixel;
+
+// __systemZoom__.
+// On Windows, if the user sets the DPI to be 125% or 150% (instead of 100%),
+// then we get an overall zoom that needs to be accounted for.
+let systemZoom = trueZoom(window);
+
+// __canBeResized(window)__.
+// Returns true iff the window is in a state that can
+// be resized. Namely, not fullscreen, not maximized,
+// and not running in a tiling window manager.
+let canBeResized = function (window) {
+ // Note that window.fullScreen and (window.windowState === window.STATE_FULLSCREEN)
+ // sometimes disagree, so we only allow resizing when both are false.
+ return !isTilingWindowManager &&
+ !window.fullScreen &&
+ window.windowState === window.STATE_NORMAL;
+};
+
+// __isDocked(window)__.
+// On Windows and some linux desktops, you can "dock" a window
+// at the right or left, so that it is maximized only in height.
+// Returns true in this case. (Note we use mozInnerScreenY instead
+// of screenY to take into account title bar space sometimes left
+// out of outerHeight in certain desktop environments.)
+let isDocked = window => ((window.mozInnerScreenY + window.outerHeight) >=
+ (window.screen.availTop + window.screen.availHeight) &&
+ (window.screenY <= window.screen.availTop));
+
+// ## Window appearance
+
+// __marginToolTip__.
+// A constant. The tooltip string shown in the margin.
+let marginToolTip = torbuttonBundle.GetStringFromName("torbutton.content_sizer.margin_tooltip");
+
+// __updateContainerAppearance(container, on)__.
+// Get the color and position of margins correct.
+let updateContainerAppearance = function (container, on) {
+ // Align the browser at top left, so any gray margin will be visible
+ // at right and bottom. Except in fullscreen, where we have black
+ // margins and gBrowser in top center, and when using a tiling
+ // window manager, when we have gray margins and gBrowser in top
+ // center.
+ container.align = on ?
+ (canBeResized(window) ? "start" : "center")
+ : "";
+ container.pack = on ? "start" : "";
+ container.tooltipText = on ? marginToolTip : "";
+};
+
+// __updateBackground(window)__.
+// Sets the margin background to black or dim gray, depending on
+// whether the window is full screen.
+let updateBackground = function (window) {
+ window.gBrowser.parentElement.style
+ .backgroundColor = window.fullScreen ? "Black" : "LightGray";
+};
+
+// ## Window Zooming
+
+// __computeTargetZoom(parentWidth, parentHeight, xStep, yStep, fillHeight)__.
+// Given a parent width and height for gBrowser's container, returns the
+// desired zoom for the content window.
+let computeTargetZoom = function (parentWidth, parentHeight, xStep, yStep, fillHeight) {
+ if (fillHeight) {
+ // Return the estimated zoom need to fill the height of the browser.
+ let h = largestMultipleLessThan(yStep, parentHeight);
+ return parentHeight / h;
+ } else {
+ // Here we attempt to find a zoom with the best fit for the window size that will
+ // provide a content window with appropriately quantized dimensions.
+ let w = largestMultipleLessThan(xStep, parentWidth),
+ h = largestMultipleLessThan(yStep, parentHeight),
+ parentAspectRatio = parentWidth / parentHeight,
+ possibilities = [[w, h],
+ [Math.min(w, w - xStep), h],
+ [w, Math.min(h - yStep)]],
+ // Find the [w, h] pair with the closest aspect ratio to the parent window.
+ score = ([w, h]) => Math.abs(Math.log(w / h / parentAspectRatio)),
+ [W, H] = sortBy(possibilities, score)[0];
+ // Return the estimated zoom.
+ return Math.min(parentHeight / H, parentWidth / W);
+ }
+};
+
+// __updateDimensions(window, xStep, yStep)__.
+// Changes the width and height of the gBrowser XUL element to be a multiple of x/yStep.
+let updateDimensions = function (window, xStep, yStep) {
+ // Don't run if window is minimized.
+ if (window.windowState === window.STATE_MINIMIZED) return;
+ let gBrowser = window.gBrowser,
+ container = gBrowser.parentElement;
+ updateContainerAppearance(container, true);
+ let parentWidth = container.clientWidth,
+ parentHeight = container.clientHeight,
+ longPage = !gBrowser.contentWindow.fullScreen,
+ targetZoom = (canBeResized(window) && !isDocked(window)) ?
+ 1 : computeTargetZoom(parentWidth,
+ parentHeight, xStep, yStep, longPage),
+ zoomOffset = 1;
+ for (let i = 0; i < 8; ++i) {
+ // We set `gBrowser.fullZoom` to 99% of the needed zoom, unless
+ // it's `1`. That's because the "true zoom" is sometimes larger
+ // than fullZoom, and we need to ensure the gBrowser width and
+ // height do not exceed the container size.
+ gBrowser.fullZoom = (targetZoom === 1 ? 1 : 0.99) * targetZoom * zoomOffset;
+ currentDefaultZoom = gBrowser.fullZoom;
+ let zoom = trueZoom(gBrowser.contentWindow) / systemZoom,
+ targetContentWidth = largestMultipleLessThan(xStep, parentWidth / zoom),
+ targetContentHeight = largestMultipleLessThan(yStep, parentHeight / zoom),
+ targetBrowserWidth = Math.round(targetContentWidth * zoom),
+ targetBrowserHeight = Math.round(targetContentHeight * zoom);
+ // Because gBrowser is inside a vbox, width and height behave differently. It turns
+ // out we need to set `gBrowser.width` and `gBrowser.maxHeight`.
+ gBrowser.width = targetBrowserWidth;
+ gBrowser.maxHeight = targetBrowserHeight;
+ // When using Windows DPI != 100%, we can get rounding errors. We'll need
+ // to try again if we failed to get rounded content width x height.
+ // Unfortunately, this is not detectable if search bar or dev console is open.
+ if ((// Some weird sidebar is open, or
+ gBrowser.clientWidth !== gBrowser.selectedBrowser.clientWidth ||
+ // content width is correct.
+ gBrowser.contentWindow.innerWidth === targetContentWidth) &&
+ (// Search bar or dev console is open, or
+ gBrowser.clientHeight !== gBrowser.selectedBrowser.clientHeight ||
+ // content height is correct.
+ gBrowser.contentWindow.innerHeight === targetContentHeight)) {
+ logger.eclog(3,
+ " chromeWin " + window.outerWidth + "x" + window.outerHeight +
+ " container " + parentWidth + "x" + parentHeight +
+ " gBrowser.fullZoom " + gBrowser.fullZoom + "X" +
+ " targetContent " + targetContentWidth + "x" + targetContentHeight +
+ " zoom " + zoom + "X" +
+ " targetBrowser " + targetBrowserWidth + "x" + targetBrowserHeight +
+ " gBrowser " + gBrowser.clientWidth + "x" + gBrowser.clientHeight +
+ " content " + gBrowser.contentWindow.innerWidth + "x" + gBrowser.contentWindow.innerHeight);
+ break;
+ }
+ zoomOffset *= 1.02;
+ }
+};
+
+// __resetZoomOnDomainChanges(gBrowser, on)__.
+// If `on` is true, then every time a tab location changes
+// to a new domain, the tab's zoom level is set back to the
+// "default zoom" level.
+let resetZoomOnDomainChanges = (function () {
+ let tabToDomainMap = new Map(),
+ onLocationChange = function (browser) {
+ let lastHost = tabToDomainMap.get(browser),
+ currentHost = browser &&
+ browser.currentURI &&
+ browser.currentURI.asciiHost;
+ if (lastHost !== currentHost) {
+ browser.fullZoom = currentDefaultZoom;
+ // Record the tab's current domain, so that we
+ // can see when it changes.
+ tabToDomainMap.set(browser, currentHost);
+ }
+ },
+ listener = { onLocationChange : onLocationChange };
+ return function (gBrowser, on) {
+ if (on) {
+ gBrowser.addTabsProgressListener(listener);
+ } else {
+ gBrowser.removeTabsProgressListener(listener);
+ }
+ };
+})();
+
+// ## Window Resizing
// __reshape(window, {left, top, width, height}, timeoutMs)__.
// Reshapes the window to rectangle {left, top, width, height} and yields
@@ -73,7 +331,10 @@ let reshape = function* (window, {left, top, width, height}, timeoutMs) {
h = isNumber(height) ? height : window.outerHeight;
// Make sure we are in a new event.
yield sleep(0);
- if (w !== window.outerWidth || h !== window.outerWidth) {
+ // Sometimes we get a race condition in linux when maximizing,
+ // so check again at the last minute that resizing is allowed.
+ if (!canBeResized(window)) return;
+ if (w !== window.outerWidth || h !== window.outerHeight) {
window.resizeTo(w, h);
}
if (x !== window.screenX || y !== window.screenY) {
@@ -87,27 +348,17 @@ let reshape = function* (window, {left, top, width, height}, timeoutMs) {
h !== window.outerHeight) {
let timeLeft = finishTime - Date.now();
if (timeLeft <= 0) break;
- yield listen(window, "resize", true, timeLeft);
+ yield listenForTrueResize(window, timeLeft);
}
};
-// __rebuild(window)__.
-// Jog the size of the window slightly, to remind the window manager
-// to redraw the window.
-let rebuild = function* (window) {
- let h = window.outerHeight;
- yield reshape(window, {height : (h + 1)}, 300);
- yield reshape(window, {height : h}, 300);
-};
-
// __gaps(window)__.
// Deltas between gBrowser and its container. Returns null if there is no gap.
let gaps = function (window) {
let gBrowser = window.gBrowser,
container = gBrowser.parentElement,
- deltaWidth = Math.max(0, container.clientWidth - gBrowser.clientWidth - 1),
- deltaHeight = Math.max(0, container.clientHeight - gBrowser.clientHeight - 1);
- //logger.eclog(3, "gaps " + deltaWidth + "," + deltaHeight);
+ deltaWidth = Math.max(0, container.clientWidth - gBrowser.clientWidth),
+ deltaHeight = Math.max(0, container.clientHeight - gBrowser.clientHeight);
return (deltaWidth === 0 && deltaHeight === 0) ? null
: { deltaWidth : deltaWidth, deltaHeight : deltaHeight };
};
@@ -115,170 +366,142 @@ let gaps = function (window) {
// __shrinkwrap(window)__.
// Shrinks the window so that it encloses the gBrowser with no gaps.
let shrinkwrap = function* (window) {
- // Maximized windows in Linux and Windows need to be demaximized first.
- if (gaps(window) &&
- window.windowState === 1 && /* maximized */
- Services.appinfo.OS !== "Darwin") {
- if (Services.appinfo.OS !== "WINNT") {
- // Linux windows need an extra jolt out of maximized mode.
- window.moveBy(1,1);
- }
- // If window has been maximized, demaximize by shrinking it to
- // fit within the available screen area.
- yield reshape(window,
- {left : window.screen.availLeft + 1,
- top : window.screen.availTop + 1,
- width : window.screen.availWidth - 2,
- height : window.screen.availHeight - 2},
- 500);
- }
// Figure out what size change we need.
- let currentGaps = gaps(window);
+ let currentGaps = gaps(window),
+ screenRightEdge = window.screen.availWidth + window.screen.availLeft,
+ windowRightEdge = window.screenX + window.outerWidth;
if (currentGaps) {
// Now resize to close the gaps.
yield reshape(window,
{width : (window.outerWidth - currentGaps.deltaWidth),
- height : (window.outerHeight - currentGaps.deltaHeight)},
+ // Shrink in height only if we are not docked.
+ height : !isDocked(window) ?
+ (window.outerHeight -
+ currentGaps.deltaHeight) : null,
+ left : (isDocked(window) &&
+ (windowRightEdge >= screenRightEdge)) ?
+ (window.screenX + currentGaps.deltaWidth)
+ : null },
500);
}
};
-// __updateContainerAppearance(container, on)__.
-// Get the color and position of margins right.
-let updateContainerAppearance = function (container, on) {
- // Align the browser at top left, so any gray margin will be visible
- // at right and bottom. Except in fullscreen, where we have black
- // margins and gBrowser in top center.
- container.align = on ? (window.fullScreen ? "center" : "start")
- : "";
- container.pack = on ? "start" : "";
- container.style.backgroundColor = on ? (window.fullScreen ? "Black"
- : "DimGray")
- : "";
+// __rebuild(window)__.
+// Jog the size of the window slightly, to remind the window manager
+// to redraw the window.
+let rebuild = function* (window) {
+ let h = window.outerHeight;
+ yield reshape(window, {height : (h + 1)}, 300);
+ yield reshape(window, {height : h}, 300);
};
// __fixWindow(window)__.
// An async function for Task.jsm. Makes sure the window looks okay
// given the quantized browser element.
let fixWindow = function* (window) {
- updateContainerAppearance(window.gBrowser.parentElement, true);
- if (!window.fullScreen) {
+ if (canBeResized(window)) {
yield shrinkwrap(window);
- if (Services.appinfo.OS !== "Darwin" && Services.appinfo.OS !== "WINNT") {
- // Linux tends to require us to rebuild the window, or we might be
- // left with a large useless white area on the screen.
- yield rebuild(window);
+ if (!isMac && !isWindows) {
+ // Unfortunately, on some linux desktops,
+ // the window resize fails if the user is still holding on
+ // to the drag-resize handle. Even more unfortunately, the
+ // only way to know that the user if finished dragging
+ // if we detect the mouse cursor inside the window or the
+ // user presses a key.
+ // So, after the first mousemove, or keydown event occurs, we
+ // rebuild the window.
+ let event = yield Promise.race(
+ [listen(window, "mousemove", true),
+ listen(window, "keydown", true),
+ listen(window, "resize", true)]);
+ if (event !== "resize") {
+ yield rebuild(window);
+ }
+ return event;
}
}
};
// __autoresize(window, stepMs)__.
-// Do what it takes to eliminate the gray margin around the gBrowser inside
-// window. Periodically (stepMs) attempt to shrink the window. Runs
-// as a Task.jsm coroutine.
-let autoresize = function (window, stepMs) {
+// Automatically resize the gBrowser, and then shrink the window
+// if the user has attempted to resize it.
+let autoresize = function (window, stepMs, xStep, yStep) {
let stop = false;
Task.spawn(function* () {
+ // Fix the content dimensions once at startup, and
+ // keep updating the dimensions whenever the user resizes
+ // the window.
while (!stop) {
+ updateDimensions(window, xStep, yStep);
+ let event = yield fixWindow(window);
// Do nothing until the user starts to resize window.
- let event = yield listen(window, "resize", true);
- // Here we wrestle with the window size. If the user has released the
- // mouse cursor on the window's drag/resize handle, then fixWindow
- // will resize the window on its first call. Unfortunately, on some
- // OSs, the window resize fails if the user is still holding on
- // to the drag-resize handle. Even more unfortunately, the
- // only way to know that the user no longer has the mouse down
- // on the window's drag/resize handle is if we detect the mouse
- // cursor inside the window. So until the window fires a mousemove
- // event, we repeatedly call fixWindow every stepMs.
- while (event.type !== "mousemove") {
- event = yield Promise.race(
- [listen(window, "resize", true, stepMs),
- listen(window, "mousemove", true, stepMs)]);
- // If the user has stopped resizing the window after `stepMs`, then we can resize
- // the window so no gray margin is visible.
- if (event.type === "timeout" || event.type === "mousemove") {
- yield fixWindow(window);
+ if ((!event || event.type !== "resize") && !stop) {
+ event = yield listenForTrueResize(window);
+ }
+ if (!isTilingWindowManager) {
+ while (event.type !== "timeout" && !stop) {
+ if (!stop) {
+ updateDimensions(window, xStep, yStep);
+ event = yield listenForTrueResize(window, stepMs);
+ }
}
}
+ // The user has likely released the mouse cursor on the window's
+ // drag/resize handle, so loop and call fixWindow.
}
});
return () => { stop = true; };
};
-// __updateDimensions(gBrowser, xStep, yStep)__.
-// Changes the width and height of the gBrowser XUL element to be a multiple of x/yStep.
-let updateDimensions = function (gBrowser, xStep, yStep) {
- // TODO: Get zooming to work such that it doesn't cause the window
- // to continuously shrink.
- // We'll use something like:
- // let winUtils = gBrowser.contentWindow
- // .QueryInterface(Components.interfaces.nsIInterfaceRequestor)
- // .getInterface(Components.interfaces.nsIDOMWindowUtils),
- // zoom = winUtils.screenPixelsPerCSSPixel,
- let zoom = 1,
- parentWidth = gBrowser.parentElement.clientWidth,
- parentHeight = gBrowser.parentElement.clientHeight,
- targetContentWidth = largestMultipleLessThan(xStep, parentWidth / zoom),
- targetContentHeight = largestMultipleLessThan(yStep, parentHeight / zoom),
- targetBrowserWidth = targetContentWidth * zoom,
- targetBrowserHeight = targetContentHeight * zoom;
- // Because gBrowser is inside a vbox, width and height behave differently. It turns
- // out we need to set `gBrowser.width` and `gBrowser.maxHeight`.
- gBrowser.width = targetBrowserWidth;
- gBrowser.maxHeight = targetBrowserHeight;
- // If the content window's innerWidth/innerHeight failed to updated correctly,
- // then jog the gBrowser width/height. (With zoom there may also be a rounding
- // error, but we can't do much about that.)
- if (gBrowser.contentWindow.innerWidth !== targetContentWidth ||
- gBrowser.contentWindow.innerHeight !== targetContentHeight) {
- gBrowser.width = targetBrowserWidth + 1;
- gBrowser.maxHeight = gBrowser.targetBrowserHeight + 1;
- gBrowser.width = targetBrowserWidth;
- gBrowser.maxHeight = targetBrowserHeight;
- }
- logger.eclog(3, "zoom " + zoom + "X" +
- " chromeWin " + window.outerWidth + "x" + window.outerHeight +
- " container " + parentWidth + "x" + parentHeight +
- " gBrowser " + gBrowser.clientWidth + "x" + gBrowser.clientHeight +
- " content " + gBrowser.contentWindow.innerWidth + "x" + gBrowser.contentWindow.innerHeight);
-};
+// ## Main Function
-// __quantizeBrowserSizeNow(window, xStep, yStep)__.
+// __quantizeBrowserSizeMain(window, xStep, yStep)__.
// Ensures that gBrowser width and height are multiples of xStep and yStep, and always as
// large as possible inside the chrome window.
let quantizeBrowserSizeMain = function (window, xStep, yStep) {
let gBrowser = window.gBrowser,
container = window.gBrowser.parentElement,
- updater = event => updateDimensions(gBrowser, xStep, yStep),
- originalMinWidth = gBrowser.minWidth,
- originalMinHeight = gBrowser.minHeight,
+ fullscreenHandler = function () {
+ // Use setTimeout to make sure we only update dimensions after
+ // full screen mode is fully established.
+ window.setTimeout(function () {
+ updateDimensions(window, xStep, yStep);
+ updateBackground(window);
+ }, 0);
+ },
+ originalMinWidth = container.minWidth,
+ originalMinHeight = container.minHeight,
stopAutoresizing,
activate = function (on) {
+ console.log("activate:", on);
// Don't let the browser shrink below a single xStep x yStep size.
- gBrowser.minWidth = on ? xStep : originalMinWidth;
- gBrowser.minHeight = on ? yStep : originalMinHeight;
+ container.minWidth = on ? xStep : originalMinWidth;
+ container.minHeight = on ? yStep : originalMinHeight;
updateContainerAppearance(container, on);
+ updateBackground(window);
+ resetZoomOnDomainChanges(gBrowser, on);
if (on) {
- // Quantize browser size on activation.
- updateDimensions(gBrowser, xStep, yStep);
shrinkwrap(window);
- // Quantize browser size at subsequent resize events.
- window.addEventListener("resize", updater, false);
- stopAutoresizing = autoresize(window, 250);
+ window.addEventListener("sizemodechange", fullscreenHandler, false);
+ stopAutoresizing = autoresize(window,
+ (isMac || isWindows) ? 250 : 500,
+ xStep, yStep);
+ console.log("activated");
} else {
if (stopAutoresizing) stopAutoresizing();
// Ignore future resize events.
- window.removeEventListener("resize", updater, false);
+ window.removeEventListener("sizemodechange", fullscreenHandler, false);
// Let gBrowser expand with its parent vbox.
gBrowser.width = "";
gBrowser.maxHeight = "";
+ console.log("deactivated");
}
};
- bindPrefAndInit("extensions.torbutton.resize_windows", activate);
+ let unbind = bindPrefAndInit("extensions.torbutton.resize_windows", activate);
+ window.addEventListener("unload", unbind, true);
};
quantizeBrowserSizeMain(window, xStep, yStep);
-// quantizeBrowserSize
+// end of quantizeBrowserSize definition
};
diff --git a/src/chrome/content/torbutton.js b/src/chrome/content/torbutton.js
index c580c59..3454613 100644
--- a/src/chrome/content/torbutton.js
+++ b/src/chrome/content/torbutton.js
@@ -644,8 +644,6 @@ function torbutton_init() {
createTorCircuitDisplay(m_tb_control_host, m_tb_control_port, m_tb_control_pass,
"extensions.torbutton.display_circuit");
- quantizeBrowserSize(window, 200, 100);
-
torbutton_log(3, 'init completed');
}
@@ -3000,8 +2998,8 @@ function torbutton_new_tab(event)
// Returns true if the window wind is neither maximized, full screen,
// ratpoisioned/evilwmed, nor minimized.
function torbutton_is_windowed(wind) {
- torbutton_log(3, "Window: ("+wind.outerHeight+","+wind.outerWidth+") ?= ("
- +wind.screen.availHeight+","+wind.screen.availWidth+")");
+ torbutton_log(3, "Window: (" + wind.outerWidth + "," + wind.outerHeight + ") ?= ("
+ + wind.screen.availWidth + "," + wind.screen.availHeight + ")");
if(wind.windowState == Components.interfaces.nsIDOMChromeWindow.STATE_MINIMIZED
|| wind.windowState == Components.interfaces.nsIDOMChromeWindow.STATE_MAXIMIZED) {
torbutton_log(2, "Window is minimized/maximized");
@@ -3391,7 +3389,8 @@ var torbutton_resizelistener =
var progress =
Components.classes["@mozilla.org/docloaderservice;1"].
getService(Components.interfaces.nsIWebProgress);
- var win = getBrowser().contentWindow;
+ var win = getBrowser().contentWindow,
+ container = getBrowser().parentElement;
if (!win || typeof(win) == "undefined") {
torbutton_log(5, "No initial browser content window?");
progress.removeProgressListener(this);
@@ -3416,8 +3415,8 @@ var torbutton_resizelistener =
" Available: " + availWidth.value + "x" +
availHeight.value);
- var diff_height = window.outerHeight - win.innerHeight;
var diff_width = window.outerWidth - win.innerWidth;
+ var diff_height = window.outerHeight - win.innerHeight;
var delta_fix = 0;
// The following block tries to cope with funny corner cases where the
@@ -3481,6 +3480,14 @@ var torbutton_resizelistener =
height = Math.floor(maxHeight/100.0)*100;
}
+ let resizeInnerWindowTo = function (width, height) {
+ window.resizeBy(width - win.innerWidth,
+ height - win.innerHeight);
+ torbutton_log(3, "Resized new window from: " + container.clientWidth + "x" +
+ container.clientHeight + " to " + width + "x" + height +
+ " in state " + window.windowState);
+ }
+
m_tb_resize_handler = function() {
if (window.windowState === 1) {
if (m_tb_prefs.
@@ -3537,7 +3544,7 @@ var torbutton_resizelistener =
getBoolPref(k_tb_tor_resize_warn_pref)) {
window.addEventListener("resize",
function() {
- win.resizeBy(width - win.innerWidth, height - win.innerHeight);
+ resizeInnerWindowTo(width, height);
var calling_function = arguments.callee;
setTimeout(function() {
torbutton_log(3, "Removing resize listener..");
@@ -3575,10 +3582,7 @@ var torbutton_resizelistener =
// This is fun. any attempt to directly set the inner window actually
// resizes the outer width to that value instead. Must use resizeBy()
// instead of assignment or resizeTo()
- win.resizeBy(width - win.innerWidth, height - win.innerHeight);
- torbutton_log(3, "Resized new window from: " + win.innerWidth + "x" +
- win.innerHeight + " to " + width + "x" + height +
- " in state " + window.windowState);
+ resizeInnerWindowTo(width, height);
// Resizing within this progress listener does not always work as overlays
// of other extensions might still influence the height/width of the
@@ -3592,14 +3596,9 @@ var torbutton_resizelistener =
function(mutations) {
mutations.forEach(
function(mutation) {
- torbutton_log(3, "Mutation observer: Window dimensions are: " +
- win.innerWidth + " x " + win.innerHeight);
setTimeout(function() {
- win.resizeBy(width - win.innerWidth,
- height - win.innerHeight);
- torbutton_log(3, "Mutation observer: Window " +
- "dimensions are (after resizing again): " + win.
- innerWidth + " x " + win.innerHeight);
+ resizeInnerWindowTo(width, height);
+ quantizeBrowserSize(window, 100, 100);
}, 0);
mut_observer.disconnect();
}
diff --git a/src/chrome/locale/en/torbutton.properties b/src/chrome/locale/en/torbutton.properties
index ca048df..76c0c0d 100644
--- a/src/chrome/locale/en/torbutton.properties
+++ b/src/chrome/locale/en/torbutton.properties
@@ -7,6 +7,7 @@ torbutton.circuit_display.this_browser = This browser
torbutton.circuit_display.relay = relay
torbutton.circuit_display.tor_bridge = Bridge
torbutton.circuit_display.unknown_country = Unknown country
+torbutton.content_sizer.margin_tooltip = Tor Browser adds this margin to make the width and height of your window less distinctive, and thus reduces the ability of people to track you online.
torbutton.panel.tooltip.disabled = Click to enable Tor
torbutton.panel.tooltip.enabled = Click to disable Tor
torbutton.panel.plugins.disabled = Click to enable plugins
diff --git a/src/modules/utils.js b/src/modules/utils.js
index 7a27326..de3cca6 100644
--- a/src/modules/utils.js
+++ b/src/modules/utils.js
@@ -39,5 +39,18 @@ let bindPrefAndInit = function (prefName, prefHandler) {
return () => { prefs.removeObserver(prefName, observer); };
};
+// ## Environment variables
+
+// __env__.
+// Provides access to process environment variables.
+let env = Components.classes["@mozilla.org/process/environment;1"]
+ .getService(Components.interfaces.nsIEnvironment);
+
+// __getEnv(name)__.
+// Reads the environment variable of the given name.
+let getEnv = function (name) {
+ return env.exists(name) ? env.get(name) : undefined;
+};
+
// Export utility functions for external use.
-let EXPORTED_SYMBOLS = ["bindPrefAndInit", "getPrefValue"];
+let EXPORTED_SYMBOLS = ["bindPrefAndInit", "getPrefValue", "getEnv"];