Bug 1347589: Implementation of Marionette Set Window Rect. r?ato draft
authorDavid Burns <dburns@mozilla.com>
Fri, 24 Mar 2017 14:35:38 +0000
changeset 552469 ae93026088a39d45873746311da46aebc6180d78
parent 552467 5a479113bc0fe5cbeec4aaf297ff3d9b9155c6c9
child 552470 7f90019aca5f64a74ead9452fd93fd9e5de6cc36
push id51348
push userbmo:dburns@mozilla.com
push dateTue, 28 Mar 2017 12:58:06 +0000
reviewersato
bugs1347589
milestone55.0a1
Bug 1347589: Implementation of Marionette Set Window Rect. r?ato This implements Set Window Rect from the W3C WebDriver specification to allow us to change the size and position of a window in one call MozReview-Commit-ID: KxwJyyjg1VU
testing/marionette/client/marionette_driver/marionette.py
testing/marionette/driver.js
testing/marionette/harness/marionette_harness/tests/unit/test_window_position.py
--- a/testing/marionette/client/marionette_driver/marionette.py
+++ b/testing/marionette/client/marionette_driver/marionette.py
@@ -1465,18 +1465,41 @@ class Marionette(object):
             "getWindowPosition", key="value" if self.protocol == 1 else None)
 
     def set_window_position(self, x, y):
         """Set the position of the current window
 
         :param x: x coordinate for the top left of the window
         :param y: y coordinate for the top left of the window
         """
+        warnings.warn("set_window_position() has been deprecated, please use set_window_rect()",
+                      DeprecationWarning)
         self._send_message("setWindowPosition", {"x": x, "y": y})
 
+    def set_window_rect(self, x=None, y=None, height=None, width=None):
+        """Set the position and size of the current window.
+
+        The supplied width and height values refer to the window outerWidth
+        and outerHeight values, which include scroll bars, title bars, etc.
+
+        An error will be returned if the requested window size would result
+        in the window being in the maximised state.
+
+        :param x: x coordinate for the top left of the window
+        :param y: y coordinate for the top left of the window
+        :param width: The width to resize the window to.
+        :param height: The height to resize the window to.
+        """
+        if (x is None and y is None) and (height is None and width is None):
+            raise errors.InvalidArgumentException("x and y or height and width need values")
+
+        return self._send_message("setWindowRect", {"x": x, "y": y,
+                                                    "height": height,
+                                                    "width": width})
+
     @property
     def title(self):
         """Current title of the active window."""
         return self._send_message("getTitle", key="value")
 
     @property
     def window_handles(self):
         """Get list of windows in the current context.
@@ -2157,26 +2180,28 @@ class Marionette(object):
         :returns: dictionary representation of current window width and height
         """
         return self._send_message("getWindowSize",
                                   key="value" if self.protocol == 1 else None)
 
     def set_window_size(self, width, height):
         """Resize the browser window currently in focus.
 
-        The supplied width and height values refer to the window outerWidth
-        and outerHeight values, which include scroll bars, title bars, etc.
+        The supplied ``width`` and ``height`` values refer to the window `outerWidth`
+        and `outerHeight` values, which include scroll bars, title bars, etc.
 
         An error will be returned if the requested window size would result
         in the window being in the maximised state.
 
         :param width: The width to resize the window to.
         :param height: The height to resize the window to.
 
         """
+        warnings.warn("set_window_size() has been deprecated, please use set_window_rect()",
+                      DeprecationWarning)
         body = {"width": width, "height": height}
         return self._send_message("setWindowSize", body)
 
     def maximize_window(self):
         """ Resize the browser window currently receiving commands. The action
         should be equivalent to the user pressing the the maximize button
         """
         return self._send_message("maximizeWindow")
--- a/testing/marionette/driver.js
+++ b/testing/marionette/driver.js
@@ -1257,49 +1257,82 @@ GeckoDriver.prototype.getWindowPosition 
 
   return {
     x: win.screenX,
     y: win.screenY,
   };
 };
 
 /**
- * Set the window position of the browser on the OS Window Manager
+ * Set the window position and size of the browser on the OS Window Manager
  *
+ * The supplied width and height values refer to the window outerWidth
+ * and outerHeight values, which include browser chrome and OS-level
+ * window borders.
  * @param {number} x
  *     X coordinate of the top/left of the window that it will be
  *     moved to.
  * @param {number} y
  *     Y coordinate of the top/left of the window that it will be
  *     moved to.
  *
  * @return {Object.<string, number>}
- *     Object with |x| and |y| coordinates.
+ *     Object with |x| and |y| coordinates
+ *     and |width| and |height| dimensions
+ *
  */
-GeckoDriver.prototype.setWindowPosition = function* (cmd, resp) {
+GeckoDriver.prototype.setWindowRect = function* (cmd, resp) {
   assert.firefox()
 
-  let {x, y} = cmd.parameters;
-  assert.integer(x);
-  assert.integer(y);
-
-  let win = this.getCurrentWindow();
-  let orig = {screenX: win.screenX, screenY: win.screenY};
-
-  win.moveTo(x, y);
-  yield wait.until((resolve, reject) => {
-    if ((x == win.screenX && y == win.screenY) ||
-      (win.screenX != orig.screenX || win.screenY != orig.screenY)) {
-      resolve();
-    } else {
-      reject();
-    }
-  });
-
-  return this.curBrowser.position;
+  let win = assert.window(this.getCurrentWindow());
+
+  let {x, y, height, width} = cmd.parameters;
+
+  if (height != null && width != null) {
+    assert.positiveInteger(height);
+    assert.positiveInteger(width);
+    yield new Promise(resolve => {
+      // When the DOM resize event claims that it fires _after_ the document
+      // view has been resized, it is lying.
+      //
+      // Because resize events fire at a high rate, DOM modifications
+      // such as updates to outerWidth/outerHeight are not guaranteed to
+      // have processed.  To overcome this... abomination... of the web
+      // platform, we throttle the event using setTimeout.  If everything
+      // was well in this world we would use requestAnimationFrame, but
+      // it does not seem to like our particular flavour of XUL.
+      const fps15 = 66;
+      const synchronousResize = () => win.setTimeout(resolve, fps15);
+      win.addEventListener("resize", synchronousResize, {once: true});
+      win.resizeTo(width, height);
+    });
+  }
+
+  if (x != null && y != null) {
+    assert.integer(x);
+    assert.integer(y);
+    let orig = {screenX: win.screenX, screenY: win.screenY};
+    win.moveTo(x, y);
+    yield wait.until((resolve, reject) => {
+      if ((x == win.screenX && y == win.screenY) ||
+        (win.screenX != orig.screenX || win.screenY != orig.screenY)) {
+        resolve();
+      } else {
+        reject();
+      }
+    });
+  }
+
+  return {
+    "x": win.screenX,
+    "y": win.screenY,
+    "width": win.outerWidth,
+    "height": win.outerHeight,
+  };
+
 };
 
 /**
  * Switch current top-level browsing context by name or server-assigned ID.
  * Searches for windows by name, then ID.  Content windows take precedence.
  *
  * @param {string} name
  *     Target name or ID of the window to switch to.
@@ -2559,59 +2592,16 @@ GeckoDriver.prototype.getWindowSize = fu
   let win = assert.window(this.getCurrentWindow());
   return {
     width: win.outerWidth,
     height: win.outerHeight,
   };
 };
 
 /**
- * Set the size of the browser window currently in focus.
- *
- * The supplied width and height values refer to the window outerWidth
- * and outerHeight values, which include browser chrome and OS-level
- * window borders.
- *
- * @param {number} width
- *     Requested window outer width.
- * @param {number} height
- *     Requested window outer height.
- *
- * @return {Map.<string, number>}
- *     New outerWidth/outerHeight dimensions.
- */
-GeckoDriver.prototype.setWindowSize = function* (cmd, resp) {
-  assert.firefox()
-  let win = assert.window(this.getCurrentWindow());
-
-  const {width, height} = cmd.parameters;
-
-  yield new Promise(resolve => {
-    // When the DOM resize event claims that it fires _after_ the document
-    // view has been resized, it is lying.
-    //
-    // Because resize events fire at a high rate, DOM modifications
-    // such as updates to outerWidth/outerHeight are not guaranteed to
-    // have processed.  To overcome this... abomination... of the web
-    // platform, we throttle the event using setTimeout.  If everything
-    // was well in this world we would use requestAnimationFrame, but
-    // it does not seem to like our particular flavour of XUL.
-    const fps15 = 66;
-    const synchronousResize = () => win.setTimeout(resolve, fps15);
-    win.addEventListener("resize", synchronousResize, {once: true});
-    win.resizeTo(width, height);
-  });
-
-  return {
-    width: win.outerWidth,
-    height: win.outerHeight,
-  };
-};
-
-/**
  * Maximizes the user agent window as if the user pressed the maximise
  * button.
  *
  * Not Supported on B2G or Fennec.
  */
 GeckoDriver.prototype.maximizeWindow = function (cmd, resp) {
   assert.firefox()
   let win = assert.window(this.getCurrentWindow());
@@ -3021,17 +3011,18 @@ GeckoDriver.prototype.commands = {
   "goForward": GeckoDriver.prototype.goForward,
   "refresh":  GeckoDriver.prototype.refresh,
   "getWindowHandle": GeckoDriver.prototype.getWindowHandle,
   "getChromeWindowHandle": GeckoDriver.prototype.getChromeWindowHandle,
   "getCurrentChromeWindowHandle": GeckoDriver.prototype.getChromeWindowHandle,
   "getWindowHandles": GeckoDriver.prototype.getWindowHandles,
   "getChromeWindowHandles": GeckoDriver.prototype.getChromeWindowHandles,
   "getWindowPosition": GeckoDriver.prototype.getWindowPosition,
-  "setWindowPosition": GeckoDriver.prototype.setWindowPosition,
+  "setWindowPosition": GeckoDriver.prototype.setWindowRect, // Redirecting for compatibility
+  "setWindowRect": GeckoDriver.prototype.setWindowRect,
   "getActiveFrame": GeckoDriver.prototype.getActiveFrame,
   "switchToFrame": GeckoDriver.prototype.switchToFrame,
   "switchToParentFrame": GeckoDriver.prototype.switchToParentFrame,
   "switchToWindow": GeckoDriver.prototype.switchToWindow,
   "switchToShadowRoot": GeckoDriver.prototype.switchToShadowRoot,
   "deleteSession": GeckoDriver.prototype.deleteSession,
   "importScript": GeckoDriver.prototype.importScript,
   "clearImportedScripts": GeckoDriver.prototype.clearImportedScripts,
@@ -3043,17 +3034,17 @@ GeckoDriver.prototype.commands = {
   "addCookie": GeckoDriver.prototype.addCookie,
   "getCookies": GeckoDriver.prototype.getCookies,
   "deleteAllCookies": GeckoDriver.prototype.deleteAllCookies,
   "deleteCookie": GeckoDriver.prototype.deleteCookie,
   "getActiveElement": GeckoDriver.prototype.getActiveElement,
   "getScreenOrientation": GeckoDriver.prototype.getScreenOrientation,
   "setScreenOrientation": GeckoDriver.prototype.setScreenOrientation,
   "getWindowSize": GeckoDriver.prototype.getWindowSize,
-  "setWindowSize": GeckoDriver.prototype.setWindowSize,
+  "setWindowSize": GeckoDriver.prototype.setWindowRect, // Redirecting for compatibility
   "maximizeWindow": GeckoDriver.prototype.maximizeWindow,
   "dismissDialog": GeckoDriver.prototype.dismissDialog,
   "acceptDialog": GeckoDriver.prototype.acceptDialog,
   "getTextFromDialog": GeckoDriver.prototype.getTextFromDialog,
   "sendKeysToDialog": GeckoDriver.prototype.sendKeysToDialog,
   "acceptConnections": GeckoDriver.prototype.acceptConnections,
   "quitApplication": GeckoDriver.prototype.quit,  // deprecated, can be removed in Firefox 56
   "quit": GeckoDriver.prototype.quit,
--- a/testing/marionette/harness/marionette_harness/tests/unit/test_window_position.py
+++ b/testing/marionette/harness/marionette_harness/tests/unit/test_window_position.py
@@ -24,16 +24,41 @@ class TestWindowPosition(MarionetteTestC
         self.assertIsInstance(position["y"], int)
 
     def test_set_types(self):
         for x, y in (["a", "b"], [1.2, 3.4], [True, False], [[], []], [{}, {}]):
             print("testing invalid type position ({},{})".format(x, y))
             with self.assertRaises(InvalidArgumentException):
                 self.marionette.set_window_position(x, y)
 
+    def test_setting_window_rect_with_nulls_errors(self):
+        with self.assertRaises(InvalidArgumentException):
+            self.marionette.set_window_rect(height=None, width=None,
+                                            x=None, y=None)
+
+    def test_set_position_with_rect(self):
+        old_position = self.marionette.get_window_position()
+        wanted_position = {"x": old_position["x"] + 10, "y": old_position["y"] + 10}
+
+        new_position = self.marionette.set_window_rect(x=wanted_position["x"], y=wanted_position["y"])
+
+        self.assertNotEqual(old_position["x"], new_position["x"])
+        self.assertNotEqual(old_position["y"], new_position["y"])
+
+    def test_set_size_with_rect(self):
+        actual = self.marionette.window_size
+        width = actual["width"] - 50
+        height = actual["height"] - 50
+
+        size = self.marionette.set_window_rect(width=width, height=height)
+        self.assertEqual(size["width"], width,
+                         "New width is {0} but should be {1}".format(size["width"], width))
+        self.assertEqual(size["height"], height,
+                         "New height is {0} but should be {1}".format(size["height"], height))
+
     def test_move_to_new_position(self):
         old_position = self.marionette.get_window_position()
         new_position = {"x": old_position["x"] + 10, "y": old_position["y"] + 10}
         self.marionette.set_window_position(new_position["x"], new_position["y"])
         self.assertNotEqual(old_position["x"], new_position["x"])
         self.assertNotEqual(old_position["y"], new_position["y"])
 
     def test_move_to_existing_position(self):