[tor-commits] [torbutton/master] Bug #14429, Part 3. Improve behavior of content window resizer

mikeperry at torproject.org mikeperry at torproject.org
Sat May 9 02:51:07 UTC 2015


commit 89a15ab99da7cc73074f4e5c53260a688f15ffe9
Author: Arthur Edelstein <arthuredelstein at 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"];





More information about the tor-commits mailing list