@@ -3,28 +3,110 @@

// 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;

// __extraWindowWidth, extraWindowHeight__.
// Returns any extra width or height not included in window.outerWidth, window.outerHeight.
// (Sometimes linux ignore the size of its titlebar in window.outerHeight.)
let extraWindowWidth, extraWindowHeight = [0, 0];

// ## 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);
};

// __isMac__.
// Constant, set to true if we are using a Mac (Darwin).
let isMac = Services.appinfo.OS === "Darwin";

// Utility function
let { bindPrefAndInit } = Cu.import("resource://torbutton/modules/utils.js");
// __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 +129,205 @@ 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);
});
// __getExtraOuterSize(window)__.
// Figures out any extra parts of a window not included in
// [window.outerWidth, window.outerHeight] by faking a mouse move event.
// This gets around a bug in window.outerHeight in linux which ignores
// the title bar. Use with Task.jsm only.
let getExtraOuterSize = function* (window) {
let mouseMovePromise = listen(window, "mousemove", true, 500);
windowUtils(window).sendMouseEventToWindow("mousemove", 0, 0, 0, 0, 0, false);
let mouseMoveEvent = yield mouseMovePromise;
return [mouseMoveEvent.screenX - window.screenX - mouseMoveEvent.clientX,
mouseMoveEvent.screenY - window.screenY - mouseMoveEvent.clientY];
};

// __isNumber(value)__.
// Returns true iff the value is a number.
let isNumber = x => typeof x === "number";
// __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);
[extraWindowWidth, extraWindowHeight] = yield getExtraOuterSize(window);
return event;
};

// ## 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_FULLSCREEN &&
window.windowState !== window.STATE_MAXIMIZED;
};

// __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.
let isDocked = window => ((window.screenY + window.outerHeight +
extraWindowHeight) >= 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) {
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;
// In some situations on Windows, there are nasty rounding errors. We'll need
// to try again if we failed to get rounded content width x height.
if ((gBrowser.contentWindow.innerWidth === targetContentWidth &&
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,6 +340,9 @@ 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);
// 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.outerWidth) {
window.resizeTo(w, h);
}
@@ -87,198 +357,154 @@ 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);
// We need an extra margin of safety on Windows because
// of possible DPI settings.
safetyMargin = isWindows ? 5 : 2,
deltaWidth = Math.max(0, container.clientWidth - gBrowser.clientWidth - safetyMargin),
deltaHeight = Math.max(0, container.clientHeight - gBrowser.clientHeight - safetyMargin);
return (deltaWidth === 0 && deltaHeight === 0) ? null
: { deltaWidth : deltaWidth, deltaHeight : deltaHeight };
};

// __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") {
event = yield listenForTrueResize(window);
}
if (!isTilingWindowManager) {
while (event.type !== "timeout") {
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 () {
updateDimensions(window, xStep, yStep);
updateBackground(window);
},
originalMinWidth = container.minWidth,
originalMinHeight = container.minHeight,
stopAutoresizing,
activate = function (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);
} 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 = "";
}
};
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
};
@@ -637,8 +637,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');
}

@@ -2944,8 +2942,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");
@@ -3335,7 +3333,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);
@@ -3360,8 +3359,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
@@ -3425,6 +3424,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.
@@ -3481,7 +3488,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..");
@@ -3519,10 +3526,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
@@ -3536,14 +3540,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();
}
@@ -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
@@ -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"];