Bug 565541 (2/2) - Prevent abusing usage of moving and resizing window. r=bz
authorMounir Lamouri <mounir.lamouri@gmail.com>
Tue, 31 May 2011 14:30:20 +0200
changeset 70395 3c723f2fe07ce7e41337b2fd0f59076a2c6e5d2f
parent 70394 1c52f2d68d3949465ab6db0750ab624d6fa403a0
child 70396 633b6373e87677fa42610cbc4e796edc94823def
push id20314
push usermlamouri@mozilla.com
push dateWed, 01 Jun 2011 08:12:29 +0000
treeherdermozilla-central@840644b2b6a2 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbz
bugs565541
milestone7.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 565541 (2/2) - Prevent abusing usage of moving and resizing window. r=bz The rules are simple, callers can move or resize a window if both conditions apply: 1. the window has been created with window.open; 2. the window only contains one tab.
dom/base/nsGlobalWindow.cpp
dom/base/nsGlobalWindow.h
dom/tests/mochitest/bugs/Makefile.in
dom/tests/mochitest/bugs/test_resize_move_windows.html
dom/tests/mochitest/chrome/Makefile.in
dom/tests/mochitest/chrome/test_resize_move_windows.xul
--- a/dom/base/nsGlobalWindow.cpp
+++ b/dom/base/nsGlobalWindow.cpp
@@ -4582,22 +4582,42 @@ nsGlobalWindow::MakeScriptDialogTitle(ns
 
   // Just in case
   if (aOutTitle.IsEmpty()) {
     NS_WARNING("could not get ScriptDlgGenericHeading string from string bundle");
     aOutTitle.AssignLiteral("[Script]");
   }
 }
 
-// static
 PRBool
 nsGlobalWindow::CanMoveResizeWindows()
 {
-  if (!CanSetProperty("dom.disable_window_move_resize"))
-    return PR_FALSE;
+  // When called from chrome, we can avoid the following checks.
+  if (!nsContentUtils::IsCallerTrustedForWrite()) {
+    // Don't allow scripts to move or resize windows that were not opened by a
+    // script.
+    if (!mHadOriginalOpener) {
+      return PR_FALSE;
+    }
+
+    if (!CanSetProperty("dom.disable_window_move_resize")) {
+      return PR_FALSE;
+    }
+
+    // Ignore the request if we have more than one tab in the window.
+    nsCOMPtr<nsIDocShellTreeOwner> treeOwner;
+    GetTreeOwner(getter_AddRefs(treeOwner));
+    if (treeOwner) {
+      PRUint32 itemCount;
+      if (NS_SUCCEEDED(treeOwner->GetTargetableShellCount(&itemCount)) &&
+          itemCount > 1) {
+        return PR_FALSE;
+      }
+    }
+  }
 
   if (gMouseDown && !gDragServiceDisabled) {
     nsCOMPtr<nsIDragService> ds =
       do_GetService("@mozilla.org/widget/dragservice;1");
     if (ds) {
       gDragServiceDisabled = PR_TRUE;
       ds->Suppress();
     }
--- a/dom/base/nsGlobalWindow.h
+++ b/dom/base/nsGlobalWindow.h
@@ -719,17 +719,17 @@ protected:
   nsresult SetCSSViewportWidthAndHeight(nscoord width, nscoord height);
   // Arguments to this function should have values in device pixels
   nsresult SetDocShellWidthAndHeight(PRInt32 width, PRInt32 height);
 
   static PRBool CanSetProperty(const char *aPrefName);
 
   static void MakeScriptDialogTitle(nsAString &aOutTitle);
 
-  static PRBool CanMoveResizeWindows();
+  PRBool CanMoveResizeWindows();
 
   PRBool   GetBlurSuppression();
 
   // If aDoFlush is true, we'll flush our own layout; otherwise we'll try to
   // just flush our parent and only flush ourselves if we think we need to.
   nsresult GetScrollXY(PRInt32* aScrollX, PRInt32* aScrollY,
                        PRBool aDoFlush);
   nsresult GetScrollMaxXY(PRInt32* aScrollMaxX, PRInt32* aScrollMaxY);
--- a/dom/tests/mochitest/bugs/Makefile.in
+++ b/dom/tests/mochitest/bugs/Makefile.in
@@ -137,12 +137,13 @@ include $(topsrcdir)/config/rules.mk
 		test_bug620947.html \
 		test_bug622361.html \
 		test_bug633133.html \
 		test_bug642026.html \
 		test_bug648465.html \
 		test_bug654137.html \
 		test_window_bar.html \
 		file_window_bar.html \
+		test_resize_move_windows.html \
 		$(NULL)
 
 libs:: 	$(_TEST_FILES)
 	$(INSTALL) $(foreach f,$^,"$f") $(DEPTH)/_tests/testing/mochitest/tests/$(relativesrcdir)
new file mode 100644
--- /dev/null
+++ b/dom/tests/mochitest/bugs/test_resize_move_windows.html
@@ -0,0 +1,337 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=565541
+-->
+<head>
+  <title>Test for Bug 565541</title>
+  <script type="application/javascript" src="/MochiKit/packed.js"></script>
+  <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=565541">Mozilla Bug 565541</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+  
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 565541 **/
+
+SimpleTest.waitForExplicitFinish();
+
+var previousX, previousY, previousWidth, previousHeight;
+
+
+function backValues()
+{
+  previousX = window.screenX;
+  previousY = window.screenY;
+  previousWidth = window.innerWidth;
+  previousHeight = window.innerHeight;
+}
+
+function restoreValues()
+{
+  window.screenX = previousX;
+  window.screenY = previousY;
+  window.innerWidth = previousWidth;
+  window.innerHeight = previousHeight;
+}
+
+function getNewWidth(aWindow)
+{
+  return (aWindow.innerWidth > (screen.width / 2)) ? 100 : screen.width;
+}
+
+function getNewHeight(aWindow)
+{
+  return (aWindow.innerHeight > (screen.height / 2)) ? 100 : screen.height;
+}
+
+function getNewX(aWindow)
+{
+  return (aWindow.screenX > (screen.width / 2)) ? 0 : screen.width;
+}
+
+function getNewY(aWindow)
+{
+  return (aWindow.screenY > (screen.height / 2)) ? 0 : screen.height;
+}
+
+/**
+ * hitEventLoop is called when we want to check something but we can't rely on
+ * an event or a specific number of event loop hiting.
+ * This method can be called by specifying a condition, a test (using SimpleTest
+ * API), how many times the event loop has to be hitten and what to call next.
+ * If times < 0, the event loop will be hitten as long as the condition isn't
+ * true or the test doesn't time out.
+ */
+function hitEventLoop(condition, test, times, next) {
+  if (condition() || times == 0) {
+    test();
+    next();
+    return;
+  }
+
+  setTimeout(hitEventLoop, 0, condition, test, times - 1, next);
+}
+
+function checkChangeIsDisabled(aWindow, aNext)
+{
+  // We want to check that nothing has changed. Having a high value would take
+  // too much time.
+  var hits = 10;
+
+  var originalWidth = aWindow.innerWidth;
+  var originalHeight = aWindow.innerHeight;
+
+  var originalX = aWindow.screenX;
+  var originalY = aWindow.screenY;
+
+  var oWidth = aWindow.outerWidth;
+  var oHeight = aWindow.outerHeight;
+
+  function sizeChangeCondition() {
+    return aWindow.innerWidth != originalWidth || aWindow.innerHeight != originalHeight;
+  }
+
+  function sizeChangeTest() {
+    is(aWindow.innerWidth, originalWidth, "Window width shouldn't have changed");
+    is(aWindow.innerHeight, originalHeight, "Window height shouldn't have changed");
+  }
+
+  function posChangeCondition() {
+    return aWindow.screenX != originalX || aWindow.screenY != originalY;
+  }
+
+  function posChangeTest() {
+    is(aWindow.screenX, originalX, "Window x position shouldn't have changed");
+    is(aWindow.screenY, originalY, "Window y position shouldn't have changed");
+  }
+
+  function outerChangeCondition() {
+    return aWindow.outerWidth != oWidth || aWindow.outerHeight != oHeight;
+  }
+
+  function outerChangeTest() {
+    is(aWindow.outerWidth, oWidth, "Window outerWidth shouldn't have changed");
+    is(aWindow.outerHeight, oHeight, "Window outerHeight shouldn't have changed");
+  }
+
+  /**
+   * Size checks.
+   */
+  aWindow.innerWidth = getNewWidth(aWindow);
+  aWindow.innerHeight = getNewHeight(aWindow);
+  hitEventLoop(sizeChangeCondition, sizeChangeTest, hits, function () {
+    aWindow.resizeTo(getNewWidth(aWindow), getNewHeight(aWindow));
+
+  hitEventLoop(sizeChangeCondition, sizeChangeTest, hits, function () {
+    aWindow.resizeBy(getNewWidth(aWindow) - aWindow.innerWidth,
+                     getNewHeight(aWindow) - aWindow.innerHeight);
+
+  hitEventLoop(sizeChangeCondition, sizeChangeTest, hits, function () {
+    aWindow.sizeToContent();
+
+  hitEventLoop(sizeChangeCondition, sizeChangeTest, hits, function () {
+  /**
+   * Position checks.
+   */
+    aWindow.screenX = getNewX(aWindow);
+    aWindow.screenY = getNewY(aWindow);
+  hitEventLoop(posChangeCondition, posChangeTest, hits, function () {
+    aWindow.moveTo(getNewX(aWindow), getNewY(aWindow));
+  hitEventLoop(posChangeCondition, posChangeTest, hits, function () {
+    aWindow.moveBy(getNewX(aWindow) - aWindow.screenX,
+                   getNewY(aWindow) - aWindow.screenY);
+  hitEventLoop(posChangeCondition, posChangeTest, hits, function () {
+  /**
+   * Outer width/height checks.
+   */
+    aWindow.outerWidth *= 2;
+    aWindow.outerHeight *= 2;
+
+  hitEventLoop(outerChangeCondition, outerChangeTest, hits, aNext);
+  });
+  });
+  });
+  });
+  });
+  });
+  });
+}
+
+function checkChangeIsEnabled(aWindow, aNext)
+{
+  // Something should happen. We are not going to go to the next test until
+  // it does.
+  var hits = -1;
+
+  var prevWidth;
+  var prevHeight;
+
+  var prevX;
+  var prevY;
+
+  var oWidth;
+  var oHeight;
+
+  function sizeChangeCondition() {
+    return aWindow.innerWidth != prevWidth && aWindow.innerHeight != prevHeight;
+  }
+
+  function sizeChangeTest() {
+    isnot(aWindow.innerWidth, prevWidth, "Window width should have changed");
+    isnot(aWindow.innerHeight, prevHeight, "Window height should have changed");
+
+    prevWidth = aWindow.innerWidth;
+    prevHeight = aWindow.innerHeight;
+  }
+
+  function posChangeCondition() {
+    return aWindow.screenX != prevX && aWindow.screenY != prevY;
+  }
+
+  function posChangeTest() {
+    isnot(aWindow.screenX, prevX, "Window x position should have changed");
+    isnot(aWindow.screenY, prevY, "Window y position should have changed");
+
+    prevX = aWindow.screenX;
+    prevY = aWindow.screenY;
+  }
+
+  function outerChangeCondition() {
+    return aWindow.outerWidth != oWidth && aWindow.outerHeight != oHeight;
+  }
+
+  function outerChangeTest() {
+    isnot(aWindow.outerWidth, oWidth, "Window outerWidth should have changed");
+    isnot(aWindow.outerHeight, oHeight, "Window outerHeight should have changed");
+
+    aWindow.outerWidth = oWidth;
+    aWindow.outerHeight = oHeight;
+  }
+
+  /**
+   * Size checks.
+   */
+  prevWidth = aWindow.innerWidth;
+  prevHeight = aWindow.innerHeight;
+
+  aWindow.innerWidth = getNewWidth(aWindow);
+  aWindow.innerHeight = getNewHeight(aWindow);
+
+  hitEventLoop(sizeChangeCondition, sizeChangeTest, hits, function () {
+    aWindow.resizeTo(getNewWidth(aWindow), getNewHeight(aWindow));
+
+  hitEventLoop(sizeChangeCondition, sizeChangeTest, hits, function () {
+    aWindow.resizeBy(getNewWidth(aWindow) - aWindow.innerWidth,
+                     getNewHeight(aWindow) - aWindow.innerHeight);
+
+  hitEventLoop(sizeChangeCondition, sizeChangeTest, hits, function () {
+    prevWidth = aWindow.innerWidth = getNewWidth(aWindow);
+    prevHeight = aWindow.innerHeight = getNewHeight(aWindow);
+    aWindow.sizeToContent();
+
+  hitEventLoop(sizeChangeCondition, sizeChangeTest, hits, function () {
+  /**
+   * Position checks.
+   */
+    prevX = aWindow.screenX;
+    prevY = aWindow.screenY;
+
+    aWindow.screenX = getNewX(aWindow);
+    aWindow.screenY = getNewY(aWindow);
+
+  hitEventLoop(posChangeCondition, posChangeTest, hits, function () {
+    aWindow.moveTo(getNewX(aWindow), getNewY(aWindow));
+
+  hitEventLoop(posChangeCondition, posChangeTest, hits, function () {
+
+    aWindow.moveBy(getNewX(aWindow) - aWindow.screenX,
+                   getNewY(aWindow) - aWindow.screenY);
+
+  hitEventLoop(posChangeCondition, posChangeTest, hits, function () {
+  /**
+   * Outer width/height checks.
+   */
+    oWidth = aWindow.outerWidth;
+    oHeight = aWindow.outerHeight;
+
+    aWindow.outerWidth = oWidth * 2;
+    aWindow.outerHeight = oHeight * 2;
+
+  hitEventLoop(outerChangeCondition, outerChangeTest, hits, aNext);
+  });
+  });
+  });
+  });
+  });
+  });
+  });
+}
+
+SimpleTest.waitForFocus(function() {
+  if (screen.width <= 200 || screen.height <= 200) {
+    todo(false, "The screen is too small to run this test.");
+    SimpleTest.finish();
+  }
+
+  backValues();
+
+  // The current window can't change it's own size and position.
+  checkChangeIsDisabled(window, function() {
+    // We create a window and check that it can change its own size and position.
+    // However, passing size/position parameters to window.open should work.
+    var w = window.open("data:text/html,<script>" +
+      "function check(next) {" +
+      "  var is_range = function(aTest, aValue, aRange, aMsg) {" +
+      "    window.opener.ok(aTest < aValue + aRange && aTest > aValue - aRange, aMsg);" +
+      "  };" +
+      "  is_range(window.innerWidth, 170, 5, 'parameter width should be taken into account');" +
+      "  is_range(window.innerHeight, 170, 5, 'parameter height should be taken into account');" +
+      "  is_range(window.screenX, 120, 5, 'parameter screenX should be taken into account');" +
+      "  is_range(window.screenY, 120, 5, 'parameter screenY should be taken into account');" +
+      "  window.opener.checkChangeIsEnabled(window, next);" +
+      "} <\/script>", '',
+      'width=170,height=170,screenX=120,screenY=120');
+
+    SimpleTest.waitForFocus(function() {
+      w.check(function() {
+        // The current window can change the size and position of the created one.
+        checkChangeIsEnabled(w, function() {
+          w.close();
+
+          // If we call window.open with an empty string as a third parameter,
+          // by default, it will create a new tab instead of a new window.
+          // In that case, we shouldn't allow the caller to change the size/position.
+          w = window.open("data:text/html,<script>" +
+            "function check(next) {" +
+            "  window.opener.checkChangeIsDisabled(window, next);" +
+            "} <\/script>", '', '');
+
+          SimpleTest.waitForFocus(function() {
+            w.check(function() {
+
+              // The current window can't change the size and position of the new tab.
+              checkChangeIsDisabled(w, function() {
+                w.close();
+
+                restoreValues();
+                SimpleTest.finish();
+              });
+            });
+          }, w, false);
+        });
+      })
+    }, w, false);
+  });
+});
+
+</script>
+</pre>
+</body>
+</html>
--- a/dom/tests/mochitest/chrome/Makefile.in
+++ b/dom/tests/mochitest/chrome/Makefile.in
@@ -61,16 +61,17 @@ include $(topsrcdir)/config/rules.mk
 		test_focused_link_scroll.xul \
 		test_geolocation.xul \
 		test_activation.xul \
 		window_activation.xul \
 		test_DOMWindowCreated.xul \
 		DOMWindowCreated_chrome.xul \
 		DOMWindowCreated_content.html \
 		test_sandbox_image.xul \
+		test_resize_move_windows.xul \
 		$(NULL)
 
 ifeq (WINNT,$(OS_ARCH))
 _TEST_FILES += \
 		test_sizemode_attribute.xul \
 		sizemode_attribute.xul \
 		$(NULL)
 endif
new file mode 100644
--- /dev/null
+++ b/dom/tests/mochitest/chrome/test_resize_move_windows.xul
@@ -0,0 +1,268 @@
+<?xml version="1.0"?>
+<?xml-stylesheet type="text/css" href="chrome://global/skin"?>
+<?xml-stylesheet type="text/css" href="/tests/SimpleTest/test.css"?>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=565541
+-->
+<window title="Mozilla Bug 565541"
+        xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+  <script type="application/javascript" src="chrome://mochikit/content/MochiKit/packed.js"></script>
+  <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+  <!-- test results are displayed in the html:body -->
+  <body xmlns="http://www.w3.org/1999/xhtml">
+  <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=565541"
+     target="_blank">Mozilla Bug 565541</a>
+  </body>
+
+  <!-- test code goes here -->
+  <script type="application/javascript">
+  <![CDATA[
+
+  /** Test for Bug 565541 **/
+  var previousX, previousY, previousWidth, previousHeight;
+
+  function backValues()
+  {
+    previousX = window.top.screenX;
+    previousY = window.top.screenY;
+    previousWidth = window.top.innerWidth;
+    previousHeight = window.top.innerHeight;
+  }
+
+  function restoreValues()
+  {
+    window.top.screenX = previousX;
+    window.top.screenY = previousY;
+    window.top.innerWidth = previousWidth;
+    window.top.innerHeight = previousHeight;
+  }
+
+  function getNewWidth(aWindow)
+  {
+    return (aWindow.innerWidth > (screen.width / 2)) ? 100 : screen.width;
+  }
+
+  function getNewHeight(aWindow)
+  {
+    return (aWindow.innerHeight > (screen.height / 2)) ? 100 : screen.height;
+  }
+
+  function getNewX(aWindow)
+  {
+    return (aWindow.screenX > (screen.width / 2)) ? 0 : screen.width;
+  }
+
+  function getNewY(aWindow)
+  {
+    return (aWindow.screenY > (screen.height / 2)) ? 0 : screen.height;
+  }
+
+  /**
+   * hitEventLoop is called when we want to check something but we can't rely on
+   * an event or a specific number of event loop hiting.
+   * This method can be called by specifying a condition, a test (using SimpleTest
+   * API), how many times the event loop has to be hitten and what to call next.
+   * If times < 0, the event loop will be hitten as long as the condition isn't
+   * true or the test doesn't time out.
+   */
+  function hitEventLoop(condition, test, times, next) {
+    if (condition() || times == 0) {
+      test();
+      next();
+      return;
+    }
+
+    setTimeout(hitEventLoop, 0, condition, test, times - 1, next);
+  }
+
+  function checkChangeIsEnabled(aWindow, aNext)
+  {
+    // Something should happen. We are not going to go to the next test until
+    // it does.
+    var hits = -1;
+
+    var prevWidth;
+    var prevHeight;
+
+    var prevX;
+    var prevY;
+
+    var oWidth;
+    var oHeight;
+
+    function sizeChangeCondition() {
+      return aWindow.innerWidth != prevWidth && aWindow.innerHeight != prevHeight;
+    }
+
+    function sizeChangeTest() {
+      isnot(aWindow.innerWidth, prevWidth, "Window width should have changed");
+      isnot(aWindow.innerHeight, prevHeight, "Window height should have changed");
+
+      prevWidth = aWindow.innerWidth;
+      prevHeight = aWindow.innerHeight;
+    }
+
+    function posChangeCondition() {
+      // With GTK, sometimes, only one dimension changes.
+      if (navigator.platform.indexOf('Linux') != -1) {
+        return aWindow.screenX != prevX || aWindow.screenY != prevY;
+      }
+      return aWindow.screenX != prevX && aWindow.screenY != prevY;
+    }
+
+    function posChangeTest() {
+      // With GTK, sometimes, only one dimension changes.
+      if (navigator.platform.indexOf('Linux') != -1) {
+        // With GTK, sometimes, aWindow.screenX changes during two calls.
+        // So we call it once and save the returned value.
+        var x = aWindow.screenX;
+        var y = aWindow.screenY;
+        if (x != prevX) {
+          isnot(x, prevX, "Window x position should have changed");
+        }
+        if (y != prevY) {
+          isnot(y, prevY, "Window y position should have changed");
+        }
+      } else {
+        isnot(aWindow.screenX, prevX, "Window x position should have changed");
+        isnot(aWindow.screenY, prevY, "Window y position should have changed");
+      }
+
+      prevX = aWindow.screenX;
+      prevY = aWindow.screenY;
+    }
+
+    function outerChangeCondition() {
+      return aWindow.outerWidth != oWidth && aWindow.outerHeight != oHeight;
+    }
+
+    function outerChangeTest() {
+      isnot(aWindow.outerWidth, oWidth, "Window outerWidth should have changed");
+      isnot(aWindow.outerHeight, oHeight, "Window outerHeight should have changed");
+
+      aWindow.outerWidth = oWidth;
+      aWindow.outerHeight = oHeight;
+    }
+
+    /**
+     * Size checks.
+     */
+    prevWidth = aWindow.innerWidth;
+    prevHeight = aWindow.innerHeight;
+
+    aWindow.innerWidth = getNewWidth(aWindow);
+    aWindow.innerHeight = getNewHeight(aWindow);
+
+    hitEventLoop(sizeChangeCondition, sizeChangeTest, hits, function () {
+      aWindow.resizeTo(getNewWidth(aWindow), getNewHeight(aWindow));
+
+    hitEventLoop(sizeChangeCondition, sizeChangeTest, hits, function () {
+      aWindow.resizeBy(getNewWidth(aWindow) - aWindow.innerWidth,
+                       getNewHeight(aWindow) - aWindow.innerHeight);
+
+    hitEventLoop(sizeChangeCondition, sizeChangeTest, hits, function () {
+      prevWidth = aWindow.innerWidth = getNewWidth(aWindow);
+      prevHeight = aWindow.innerHeight = getNewHeight(aWindow);
+      aWindow.sizeToContent();
+
+    hitEventLoop(sizeChangeCondition, sizeChangeTest, hits, function () {
+    /**
+     * Position checks.
+     */
+      prevX = aWindow.screenX;
+      prevY = aWindow.screenY;
+
+      aWindow.screenX = getNewX(aWindow);
+      aWindow.screenY = getNewY(aWindow);
+
+    hitEventLoop(posChangeCondition, posChangeTest, hits, function () {
+      aWindow.moveTo(getNewX(aWindow), getNewY(aWindow));
+
+    hitEventLoop(posChangeCondition, posChangeTest, hits, function () {
+      aWindow.moveBy(getNewX(aWindow) - aWindow.screenX,
+                     getNewY(aWindow) - aWindow.screenY);
+
+    hitEventLoop(posChangeCondition, posChangeTest, hits, function () {
+    /**
+     * Outer width/height checks.
+     */
+      oWidth = aWindow.outerWidth;
+      oHeight = aWindow.outerHeight;
+
+      aWindow.outerWidth = oWidth * 2;
+      aWindow.outerHeight = oHeight * 2;
+
+    hitEventLoop(outerChangeCondition, outerChangeTest, hits, aNext);
+    });
+    });
+    });
+    });
+    });
+    });
+    });
+  }
+
+  SimpleTest.waitForExplicitFinish();
+  SimpleTest.waitForFocus(function() {
+    if (screen.width <= 200 || screen.height <= 200) {
+      todo(false, "The screen is too small to run this test.");
+      SimpleTest.finish();
+    }
+
+    backValues();
+
+    // We are in a chrome context, we can change the size and position.
+    checkChangeIsEnabled(window.top, function() {
+      // We create a window and check that the size and position can be set with
+      // window.open parameters and can be changed by the created window.
+      var w = window.open("data:text/html,<script>" +
+        "function check(next) {" +
+        "  var is_range = function(aTest, aValue, aRange, aMsg) {" +
+        "    ok(aTest < aValue + aRange && aTest > aValue - aRange, aMsg);" +
+        "  };" +
+        "  is_range(window.innerWidth, 170, 5, 'parameter width should be taken into account');" +
+        "  is_range(window.innerHeight, 170, 5, 'parameter height should be taken into account');" +
+        "  is_range(window.screenX, 120, 5, 'parameter screenX should be taken into account');" +
+        "  is_range(window.screenY, 120, 5, 'parameter screenY should be taken into account');" +
+        "  checkChangeIsEnabled(window, next);" +
+        "} <\/script>", '',
+        'width=170,height=170,screenX=120,screenY=120');
+
+      SimpleTest.waitForFocus(function() {
+        w.wrappedJSObject.ok = SimpleTest.ok;
+        w.wrappedJSObject.checkChangeIsEnabled = window.checkChangeIsEnabled;
+        w.wrappedJSObject.check(function() {
+          // The current window can change the size and position of the created one.
+          checkChangeIsEnabled(w, function() {
+            w.close();
+
+            // If we call window.open with an empty string as a third parameter,
+            // by default, it will create a new tab instead of a new window.
+            // In a chrome context, the size and position can change.
+            w = window.open("data:text/html,<script>" +
+              "function check(next) {" +
+              "  checkChangeIsEnabled(window, next);" +
+              "} <\/script>", '', '');
+
+            SimpleTest.waitForFocus(function() {
+               w.wrappedJSObject.checkChangeIsEnabled = window.checkChangeIsEnabled;
+               w.wrappedJSObject.check(function() {
+                // The current window can change the size and position of the new tab.
+                // Because we are in a chrome context.
+                checkChangeIsEnabled(w, function() {
+                  w.close();
+
+                  restoreValues();
+                  SimpleTest.finish();
+                });
+              });
+            }, w, false);
+          });
+        });
+      }, w, false);
+    });
+  });
+  ]]>
+  </script>
+</window>