Bug 1330882 - Part 4: Making the window.open() can only open rounded windows and the inner window will be automatically rounded after setting size through innerWidth/Height and outerWidth/Height when fingerprinting resistance is enabled. r?smaug,arthuredelstein draft
authorTim Huang <tihuang@mozilla.com>
Wed, 29 Mar 2017 15:43:56 +0800
changeset 552917 568572562f91ccb6e13e2bc4500e9ec7d6e5428c
parent 552916 6f2a666fada773f5c06f425f4aeed260fb083ced
child 552918 cf173a53a757d670bb4cd4edc2414644d51b0b80
push id51508
push userbmo:tihuang@mozilla.com
push dateWed, 29 Mar 2017 07:44:26 +0000
reviewerssmaug, arthuredelstein
bugs1330882
milestone55.0a1
Bug 1330882 - Part 4: Making the window.open() can only open rounded windows and the inner window will be automatically rounded after setting size through innerWidth/Height and outerWidth/Height when fingerprinting resistance is enabled. r?smaug,arthuredelstein This patch makes the size of inner windows will be automatically rounded for either window.open() with window features or setting window size through innerWidth/Height and outerWidth/Height when fingerprinting resistance is enabled. If the given value is greater the maximum available rounded size, then it will be set to the maximum value. Otherwise, the size will be set to the nearest upper 200x100. This patch also adds one helper function in nsContentUtils for calculating the rounded window dimensions. MozReview-Commit-ID: J2r3951vuNN
dom/base/nsContentUtils.cpp
dom/base/nsContentUtils.h
dom/base/nsGlobalWindow.cpp
toolkit/components/windowwatcher/nsWindowWatcher.cpp
xpfe/appshell/nsXULWindow.cpp
--- a/dom/base/nsContentUtils.cpp
+++ b/dom/base/nsContentUtils.cpp
@@ -288,16 +288,19 @@ bool nsContentUtils::sIsPerformanceTimin
 bool nsContentUtils::sIsResourceTimingEnabled = false;
 bool nsContentUtils::sIsUserTimingLoggingEnabled = false;
 bool nsContentUtils::sIsExperimentalAutocompleteEnabled = false;
 bool nsContentUtils::sIsWebComponentsEnabled = false;
 bool nsContentUtils::sPrivacyResistFingerprinting = false;
 bool nsContentUtils::sSendPerformanceTimingNotifications = false;
 bool nsContentUtils::sUseActivityCursor = false;
 
+int32_t nsContentUtils::sPrivacyMaxInnerWidth = 1000;
+int32_t nsContentUtils::sPrivacyMaxInnerHeight = 1000;
+
 uint32_t nsContentUtils::sHandlingInputTimeout = 1000;
 
 uint32_t nsContentUtils::sCookiesLifetimePolicy = nsICookieService::ACCEPT_NORMALLY;
 uint32_t nsContentUtils::sCookiesBehavior = nsICookieService::BEHAVIOR_ACCEPT;
 
 nsHtml5StringParser* nsContentUtils::sHTMLFragmentParser = nullptr;
 nsIParser* nsContentUtils::sXMLFragmentParser = nullptr;
 nsIFragmentContentSink* nsContentUtils::sXMLFragmentSink = nullptr;
@@ -586,16 +589,24 @@ nsContentUtils::Init()
                                "dom.forms.autocomplete.experimental", false);
 
   Preferences::AddBoolVarCache(&sIsWebComponentsEnabled,
                                "dom.webcomponents.enabled", false);
 
   Preferences::AddBoolVarCache(&sPrivacyResistFingerprinting,
                                "privacy.resistFingerprinting", false);
 
+  Preferences::AddIntVarCache(&sPrivacyMaxInnerWidth,
+                              "privacy.window.maxInnerWidth",
+                              1000);
+
+  Preferences::AddIntVarCache(&sPrivacyMaxInnerHeight,
+                              "privacy.window.maxInnerHeight",
+                              1000);
+
   Preferences::AddUintVarCache(&sHandlingInputTimeout,
                                "dom.event.handling-user-input-time-limit",
                                1000);
 
   Preferences::AddBoolVarCache(&sSendPerformanceTimingNotifications,
                                "dom.performance.enable_notify_performance_timing", false);
 
   Preferences::AddUintVarCache(&sCookiesLifetimePolicy,
@@ -2149,16 +2160,87 @@ nsContentUtils::ShouldResistFingerprinti
 {
   if (!aDocShell) {
     return false;
   }
   bool isChrome = nsContentUtils::IsChromeDoc(aDocShell->GetDocument());
   return !isChrome && sPrivacyResistFingerprinting;
 }
 
+/* static */
+void
+nsContentUtils::CalcRoundedWindowSizeForResistingFingerprinting(int32_t  aChromeWidth,
+                                                                int32_t  aChromeHeight,
+                                                                int32_t  aScreenWidth,
+                                                                int32_t  aScreenHeight,
+                                                                int32_t  aInputWidth,
+                                                                int32_t  aInputHeight,
+                                                                bool     aSetOuterWidth,
+                                                                bool     aSetOuterHeight,
+                                                                int32_t* aOutputWidth,
+                                                                int32_t* aOutputHeight)
+{
+  MOZ_ASSERT(aOutputWidth);
+  MOZ_ASSERT(aOutputHeight);
+
+  int32_t availContentWidth  = 0;
+  int32_t availContentHeight = 0;
+
+  availContentWidth = std::min(sPrivacyMaxInnerWidth,
+                               aScreenWidth - aChromeWidth);
+#ifdef MOZ_WIDGET_GTK
+  // In the GTK window, it will not report outside system decorations
+  // when we get available window size, see Bug 581863. So, we leave a
+  // 40 pixels space for them when calculating the available content
+  // height. It is not necessary for the width since the content width
+  // is usually pretty much the same as the chrome width.
+  availContentHeight = std::min(sPrivacyMaxInnerHeight,
+                                (-40 + aScreenHeight) - aChromeHeight);
+#else
+  availContentHeight = std::min(sPrivacyMaxInnerHeight,
+                                aScreenHeight - aChromeHeight);
+#endif
+
+  // Ideally, we'd like to round window size to 1000x1000, but the
+  // screen space could be too small to accommodate this size in some
+  // cases. If it happens, we would round the window size to the nearest
+  // 200x100.
+  availContentWidth = availContentWidth - (availContentWidth % 200);
+  availContentHeight = availContentHeight - (availContentHeight % 100);
+
+  // If aIsOuter is true, we are setting the outer window. So we
+  // have to consider the chrome UI.
+  int32_t chromeOffsetWidth = aSetOuterWidth ? aChromeWidth : 0;
+  int32_t chromeOffsetHeight = aSetOuterHeight ? aChromeHeight : 0;
+  int32_t resultWidth = 0, resultHeight = 0;
+
+  // if the original size is greater than the maximum available size, we set
+  // it to the maximum size. And if the original value is less than the
+  // minimum rounded size, we set it to the minimum 200x100.
+  if (aInputWidth > (availContentWidth + chromeOffsetWidth)) {
+    resultWidth = availContentWidth + chromeOffsetWidth;
+  } else if (aInputWidth < (200 + chromeOffsetWidth)) {
+    resultWidth = 200 + chromeOffsetWidth;
+  } else {
+    // Otherwise, we round the window to the nearest upper rounded 200x100.
+    resultWidth = NSToIntCeil((aInputWidth - chromeOffsetWidth) / 200.0) * 200 + chromeOffsetWidth;
+  }
+
+  if (aInputHeight > (availContentHeight + chromeOffsetHeight)) {
+    resultHeight = availContentHeight + chromeOffsetHeight;
+  } else if (aInputHeight < (100 + chromeOffsetHeight)) {
+    resultHeight = 100 + chromeOffsetHeight;
+  } else {
+    resultHeight = NSToIntCeil((aInputHeight - chromeOffsetHeight) / 100.0) * 100 + chromeOffsetHeight;
+  }
+
+  *aOutputWidth = resultWidth;
+  *aOutputHeight = resultHeight;
+}
+
 bool
 nsContentUtils::ThreadsafeIsCallerChrome()
 {
   return NS_IsMainThread() ?
     IsCallerChrome() :
     mozilla::dom::workers::IsCurrentThreadRunningChromeWorker();
 }
 
--- a/dom/base/nsContentUtils.h
+++ b/dom/base/nsContentUtils.h
@@ -263,16 +263,32 @@ public:
   static bool LookupBindingMember(JSContext* aCx, nsIContent *aContent,
                                   JS::Handle<jsid> aId,
                                   JS::MutableHandle<JS::PropertyDescriptor> aDesc);
 
   // Check whether we should avoid leaking distinguishing information to JS/CSS.
   static bool ShouldResistFingerprinting();
   static bool ShouldResistFingerprinting(nsIDocShell* aDocShell);
 
+  // A helper function to calculate the rounded window size for fingerprinting
+  // resistance. The rounded size is based on the chrome UI size and available
+  // screen size. If the inputWidth/Height is greater than the available content
+  // size, this will report the available content size. Otherwise, it will
+  // round the size to the nearest upper 200x100.
+  static void CalcRoundedWindowSizeForResistingFingerprinting(int32_t  aChromeWidth,
+                                                              int32_t  aChromeHeight,
+                                                              int32_t  aScreenWidth,
+                                                              int32_t  aScreenHeight,
+                                                              int32_t  aInputWidth,
+                                                              int32_t  aInputHeight,
+                                                              bool     aSetOuterWidth,
+                                                              bool     aSetOuterHeight,
+                                                              int32_t* aOutputWidth,
+                                                              int32_t* aOutputHeight);
+
   /**
    * Returns the parent node of aChild crossing document boundaries.
    * Uses the parent node in the composed document.
    */
   static nsINode* GetCrossDocParentNode(nsINode* aChild);
 
   /**
    * Do not ever pass null pointers to this method.  If one of your
@@ -2929,16 +2945,19 @@ private:
   static bool sIsExperimentalAutocompleteEnabled;
   static bool sIsWebComponentsEnabled;
   static bool sPrivacyResistFingerprinting;
   static bool sSendPerformanceTimingNotifications;
   static bool sUseActivityCursor;
   static uint32_t sCookiesLifetimePolicy;
   static uint32_t sCookiesBehavior;
 
+  static int32_t sPrivacyMaxInnerWidth;
+  static int32_t sPrivacyMaxInnerHeight;
+
   static nsHtml5StringParser* sHTMLFragmentParser;
   static nsIParser* sXMLFragmentParser;
   static nsIFragmentContentSink* sXMLFragmentSink;
 
   /**
    * True if there's a fragment parser activation on the stack.
    */
   static bool sFragmentParsingActive;
--- a/dom/base/nsGlobalWindow.cpp
+++ b/dom/base/nsGlobalWindow.cpp
@@ -14793,16 +14793,111 @@ nsGlobalWindow::SetReplaceableWindowCoor
   }
 
   int32_t value;
   if (!ValueToPrimitive<int32_t, eDefault>(aCx, aValue, &value)) {
     aError.Throw(NS_ERROR_UNEXPECTED);
     return;
   }
 
+  if (nsContentUtils::ShouldResistFingerprinting(GetDocShell())) {
+    bool innerWidthSpecified = false;
+    bool innerHeightSpecified = false;
+    bool outerWidthSpecified = false;
+    bool outerHeightSpecified = false;
+
+    if (strcmp(aPropName, "innerWidth") == 0) {
+      innerWidthSpecified = true;
+    } else if (strcmp(aPropName, "innerHeight") == 0) {
+      innerHeightSpecified = true;
+    } else if (strcmp(aPropName, "outerWidth") == 0) {
+      outerWidthSpecified = true;
+    } else if (strcmp(aPropName, "outerHeight") == 0) {
+      outerHeightSpecified = true;
+    }
+
+    if (innerWidthSpecified || innerHeightSpecified ||
+        outerWidthSpecified || outerHeightSpecified)
+    {
+      nsCOMPtr<nsIBaseWindow> treeOwnerAsWin = outer->GetTreeOwnerWindow();
+      nsCOMPtr<nsIScreen> screen;
+      nsCOMPtr<nsIScreenManager> screenMgr(
+        do_GetService("@mozilla.org/gfx/screenmanager;1"));
+      int32_t winLeft   = 0;
+      int32_t winTop    = 0;
+      int32_t winWidth  = 0;
+      int32_t winHeight = 0;
+      double scale = 1.0;
+
+
+      if (treeOwnerAsWin && screenMgr) {
+        // Acquire current window size.
+        treeOwnerAsWin->GetUnscaledDevicePixelsPerCSSPixel(&scale);
+        treeOwnerAsWin->GetPositionAndSize(&winLeft, &winTop, &winWidth, &winHeight);
+        winLeft = NSToIntRound(winHeight / scale);
+        winTop = NSToIntRound(winWidth / scale);
+        winWidth = NSToIntRound(winWidth / scale);
+        winHeight = NSToIntRound(winHeight / scale);
+
+        // Acquire content window size.
+        CSSIntSize contentSize;
+        outer->GetInnerSize(contentSize);
+
+        screenMgr->ScreenForRect(winLeft, winTop, winWidth, winHeight,
+                                 getter_AddRefs(screen));
+
+        if (screen) {
+          int32_t* targetContentWidth  = nullptr;
+          int32_t* targetContentHeight = nullptr;
+          int32_t screenWidth  = 0;
+          int32_t screenHeight = 0;
+          int32_t chromeWidth  = 0;
+          int32_t chromeHeight = 0;
+          int32_t inputWidth   = 0;
+          int32_t inputHeight  = 0;
+          int32_t unused = 0;
+
+          // Get screen dimensions (in device pixels)
+          screen->GetAvailRect(&unused, &unused, &screenWidth,
+                               &screenHeight);
+          // Convert them to CSS pixels
+          screenWidth = NSToIntRound(screenWidth / scale);
+          screenHeight = NSToIntRound(screenHeight / scale);
+
+          // Calculate the chrome UI size.
+          chromeWidth = winWidth - contentSize.width;
+          chromeHeight = winHeight - contentSize.height;
+
+          if (innerWidthSpecified || outerWidthSpecified) {
+            inputWidth = value;
+            targetContentWidth = &value;
+            targetContentHeight = &unused;
+          } else if (innerHeightSpecified || outerHeightSpecified) {
+            inputHeight = value;
+            targetContentWidth = &unused;
+            targetContentHeight = &value;
+          }
+
+          nsContentUtils::CalcRoundedWindowSizeForResistingFingerprinting(
+            chromeWidth,
+            chromeHeight,
+            screenWidth,
+            screenHeight,
+            inputWidth,
+            inputHeight,
+            outerWidthSpecified,
+            outerHeightSpecified,
+            targetContentWidth,
+            targetContentHeight
+          );
+        }
+      }
+    }
+  }
+
   (this->*aSetter)(value, aCallerType, aError);
 }
 
 void
 nsGlobalWindow::FireOnNewGlobalObject()
 {
   MOZ_ASSERT(IsInnerWindow());
 
--- a/toolkit/components/windowwatcher/nsWindowWatcher.cpp
+++ b/toolkit/components/windowwatcher/nsWindowWatcher.cpp
@@ -2400,32 +2400,62 @@ nsWindowWatcher::SizeOpenedWindow(nsIDoc
                            &screenHeight);
       // Convert them to CSS pixels
       screenLeft = NSToIntRound(screenLeft / scale);
       screenTop = NSToIntRound(screenTop / scale);
       screenWidth = NSToIntRound(screenWidth / scale);
       screenHeight = NSToIntRound(screenHeight / scale);
 
       if (aSizeSpec.SizeSpecified()) {
-        /* Unlike position, force size out-of-bounds check only if
-           size actually was specified. Otherwise, intrinsically sized
-           windows are broken. */
-        if (height < 100) {
-          height = 100;
-          winHeight = height + (sizeChromeHeight ? 0 : chromeHeight);
-        }
-        if (winHeight > screenHeight) {
-          height = screenHeight - (sizeChromeHeight ? 0 : chromeHeight);
-        }
-        if (width < 100) {
-          width = 100;
-          winWidth = width + (sizeChromeWidth ? 0 : chromeWidth);
-        }
-        if (winWidth > screenWidth) {
-          width = screenWidth - (sizeChromeWidth ? 0 : chromeWidth);
+        if (!nsContentUtils::ShouldResistFingerprinting()) {
+          /* Unlike position, force size out-of-bounds check only if
+             size actually was specified. Otherwise, intrinsically sized
+             windows are broken. */
+          if (height < 100) {
+            height = 100;
+            winHeight = height + (sizeChromeHeight ? 0 : chromeHeight);
+          }
+          if (winHeight > screenHeight) {
+            height = screenHeight - (sizeChromeHeight ? 0 : chromeHeight);
+          }
+          if (width < 100) {
+            width = 100;
+            winWidth = width + (sizeChromeWidth ? 0 : chromeWidth);
+          }
+          if (winWidth > screenWidth) {
+            width = screenWidth - (sizeChromeWidth ? 0 : chromeWidth);
+          }
+        } else {
+          int32_t targetContentWidth  = 0;
+          int32_t targetContentHeight = 0;
+
+          nsContentUtils::CalcRoundedWindowSizeForResistingFingerprinting(
+            chromeWidth,
+            chromeHeight,
+            screenWidth,
+            screenHeight,
+            width,
+            height,
+            sizeChromeWidth,
+            sizeChromeHeight,
+            &targetContentWidth,
+            &targetContentHeight
+          );
+
+          if (aSizeSpec.mInnerWidthSpecified ||
+              aSizeSpec.mOuterWidthSpecified) {
+            width = targetContentWidth;
+            winWidth = width + (sizeChromeWidth ? 0 : chromeWidth);
+          }
+
+          if (aSizeSpec.mInnerHeightSpecified ||
+              aSizeSpec.mOuterHeightSpecified) {
+            height = targetContentHeight;
+            winHeight = height + (sizeChromeHeight ? 0 : chromeHeight);
+          }
         }
       }
 
       if (left + winWidth > screenLeft + screenWidth ||
           left + winWidth < left) {
         left = screenLeft + screenWidth - winWidth;
       }
       if (left < screenLeft) {
--- a/xpfe/appshell/nsXULWindow.cpp
+++ b/xpfe/appshell/nsXULWindow.cpp
@@ -1024,72 +1024,64 @@ NS_IMETHODIMP nsXULWindow::GetAvailScree
 // Rounds window size to 1000x1000, or, if there isn't enough available
 // screen space, to a multiple of 200x100.
 NS_IMETHODIMP nsXULWindow::ForceRoundedDimensions()
 {
   if (mIsHiddenWindow) {
     return NS_OK;
   }
 
-  int32_t windowWidth, windowHeight;
-  int32_t availWidthCSS, availHeightCSS;
-  int32_t contentWidthCSS, contentHeightCSS;
-  int32_t contentWidth, contentHeight;
+  int32_t availWidthCSS    = 0;
+  int32_t availHeightCSS   = 0;
+  int32_t contentWidthCSS  = 0;
+  int32_t contentHeightCSS = 0;
+  int32_t windowWidthCSS   = 0;
+  int32_t windowHeightCSS  = 0;
   double devicePerCSSPixels = 1.0;
 
   GetUnscaledDevicePixelsPerCSSPixel(&devicePerCSSPixels);
 
   GetAvailScreenSize(&availWidthCSS, &availHeightCSS);
 
   // To get correct chrome size, we have to resize the window to a proper
   // size first. So, here, we size it to its available size.
   SetSpecifiedSize(availWidthCSS, availHeightCSS);
 
-  GetSize(&windowWidth, &windowHeight); // device pixels
+  // Get the current window size for calculating chrome UI size.
+  GetSize(&windowWidthCSS, &windowHeightCSS); // device pixels
+  windowWidthCSS = NSToIntRound(windowWidthCSS / devicePerCSSPixels);
+  windowHeightCSS = NSToIntRound(windowHeightCSS / devicePerCSSPixels);
 
-  int32_t availWidth = NSToIntRound(devicePerCSSPixels *
-                                    availWidthCSS); // device pixels
-  int32_t availHeight = NSToIntRound(devicePerCSSPixels *
-                                     availHeightCSS); // device pixels
+  // Get the content size for calculating chrome UI size.
   GetPrimaryContentSize(&contentWidthCSS, &contentHeightCSS);
 
-  contentWidth = NSToIntRound(devicePerCSSPixels *
-                              contentWidthCSS); // device pixels
-  contentHeight = NSToIntRound(devicePerCSSPixels *
-                               contentHeightCSS); // device pixels
+  // Calculate the chrome UI size.
+  int32_t chromeWidth = 0, chromeHeight = 0;
+  chromeWidth = windowWidthCSS - contentWidthCSS;
+  chromeHeight = windowHeightCSS - contentHeightCSS;
 
-  // Acquire the chrome UI size.
-  int32_t chromeWidth = windowWidth - contentWidth;
-  int32_t chromeHeight = windowHeight - contentHeight;
-
-  int maxInnerWidth = Preferences::GetInt("privacy.window.maxInnerWidth",
-                                          1000);
-  int maxInnerHeight = Preferences::GetInt("privacy.window.maxInnerHeight",
-                                           1000);
+  int32_t targetContentWidth = 0, targetContentHeight = 0;
 
-  // In the GTK window, it will not report outside system decorations when we
-  // get available window size, see Bug 581863. So, we leave a five percent
-  // space for them when calculating the available content height. It is not
-  // necessary for the width since the content width is usually pretty much
-  // the same as the chrome width.
-  int32_t availForContentWidthCSS =
-    std::min(maxInnerWidth, NSToIntRound((availWidth - chromeWidth) /
-                                         devicePerCSSPixels));
-  int32_t availForContentHeightCSS =
-    std::min(maxInnerHeight, NSToIntRound((0.95 * availHeight - chromeHeight) /
-                                          devicePerCSSPixels));
-  // Ideally, we'd like to round window size to 1000x1000, but the screen space
-  // could be too small to accommodate this size in some cases. If it happens,
-  // we would round the window size to the nearest 200x100.
-  int32_t targetContentWidth =
-    NSToIntRound(devicePerCSSPixels *
-                 (availForContentWidthCSS - (availForContentWidthCSS % 200)));
-  int32_t targetContentHeight =
-    NSToIntRound(devicePerCSSPixels *
-                 (availForContentHeightCSS - (availForContentHeightCSS % 100)));
+  // Here, we use the available screen dimensions as the input dimensions to
+  // force the window to be rounded as the maximum available content size.
+  nsContentUtils::CalcRoundedWindowSizeForResistingFingerprinting(
+    chromeWidth,
+    chromeHeight,
+    availWidthCSS,
+    availHeightCSS,
+    availWidthCSS,
+    availHeightCSS,
+    false, // aSetOuterWidth
+    false, // aSetOuterHeight
+    &targetContentWidth,
+    &targetContentHeight
+  );
+
+  targetContentWidth = NSToIntRound(targetContentWidth * devicePerCSSPixels);
+  targetContentHeight = NSToIntRound(targetContentHeight * devicePerCSSPixels);
 
   SetPrimaryContentSize(targetContentWidth, targetContentHeight);
 
   mIgnoreXULSize = true;
   mIgnoreXULSizeMode = true;
 
   return NS_OK;
 }