Bug 1381876 - Ensure Maximize Window is synchronous. r?automatedtester draft
authorAndreas Tolfsen <ato@sny.no>
Tue, 01 Aug 2017 18:30:52 +0100
changeset 641131 89c314e459df51fd77cf951a2a820d1679e1aaed
parent 641130 12eccaf162cc8bb2a84e8fcb4cc97bdeb26793a5
child 724727 cf55824ee20280305605450565916bf1d42a8631
push id72441
push userbmo:ato@sny.no
push dateSat, 05 Aug 2017 16:50:51 +0000
reviewersautomatedtester
bugs1381876
milestone57.0a1
Bug 1381876 - Ensure Maximize Window is synchronous. r?automatedtester Because the sizemodechange event is not synchronous on all platforms, we additionally need to await the window's dimensions to change. However, if the window manager does not have a notion of a maximised state, this operation too could time out. However, it is believed that the additional wait will allow for the window to transition to the required state in this scenario. MozReview-Commit-ID: KeHJMKSJfjQ
testing/marionette/driver.js
--- a/testing/marionette/driver.js
+++ b/testing/marionette/driver.js
@@ -14,17 +14,20 @@ Cu.import("resource://gre/modules/Log.js
 Cu.import("resource://gre/modules/Preferences.jsm");
 Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 
 Cu.import("chrome://marionette/content/accessibility.js");
 Cu.import("chrome://marionette/content/addon.js");
 Cu.import("chrome://marionette/content/assert.js");
 Cu.import("chrome://marionette/content/atom.js");
-Cu.import("chrome://marionette/content/browser.js");
+const {
+  browser,
+  WindowState,
+} = Cu.import("chrome://marionette/content/browser.js", {});
 Cu.import("chrome://marionette/content/capture.js");
 Cu.import("chrome://marionette/content/cert.js");
 Cu.import("chrome://marionette/content/cookie.js");
 Cu.import("chrome://marionette/content/element.js");
 const {
   ElementNotInteractableError,
   error,
   InsecureCertificateError,
@@ -43,17 +46,17 @@ Cu.import("chrome://marionette/content/e
 Cu.import("chrome://marionette/content/event.js");
 Cu.import("chrome://marionette/content/interaction.js");
 Cu.import("chrome://marionette/content/l10n.js");
 Cu.import("chrome://marionette/content/legacyaction.js");
 Cu.import("chrome://marionette/content/modal.js");
 Cu.import("chrome://marionette/content/proxy.js");
 Cu.import("chrome://marionette/content/reftest.js");
 Cu.import("chrome://marionette/content/session.js");
-Cu.import("chrome://marionette/content/wait.js");
+const {wait, TimedPromise} = Cu.import("chrome://marionette/content/wait.js", {});
 
 Cu.importGlobalProperties(["URL"]);
 
 this.EXPORTED_SYMBOLS = ["GeckoDriver", "Context"];
 
 var FRAME_SCRIPT = "chrome://marionette/content/listener.js";
 
 const CLICK_TO_START_PREF = "marionette.debugging.clicktostart";
@@ -2925,40 +2928,86 @@ GeckoDriver.prototype.minimizeWindow = f
 };
 
 /**
  * Synchronously maximizes the user agent window as if the user pressed
  * the maximize button, or restores it if it is already maximized.
  *
  * Not supported on Fennec.
  *
- * @return {Map.<string, number>}
+ * @return {Object.<string, number>}
  *     Window rect.
  *
  * @throws {UnsupportedOperationError}
  *     Not available for current application.
  * @throws {NoSuchWindowError}
  *     Top-level browsing context has been discarded.
  * @throws {UnexpectedAlertOpenError}
  *     A modal dialog is open, blocking this operation.
  */
-GeckoDriver.prototype.maximizeWindow = function* (cmd, resp) {
+GeckoDriver.prototype.maximizeWindow = async function(cmd, resp) {
   assert.firefox();
   const win = assert.window(this.getCurrentWindow());
   assert.noUserPrompt(this.dialog);
 
-  yield new Promise(resolve => {
-    win.addEventListener("resize", resolve, {once: true});
+  const origSize = {
+    outerWidth: win.outerWidth,
+    outerHeight: win.outerHeight,
+  };
+
+  // Wait for the window size to change.
+  async function windowSizeChange(from) {
+    return wait.until((resolve, reject) => {
+      let curSize = {
+        outerWidth: win.outerWidth,
+        outerHeight: win.outerHeight,
+      };
+      if (curSize.outerWidth != origSize.outerWidth ||
+          curSize.outerHeight != origSize.outerHeight) {
+        resolve();
+      } else {
+        reject();
+      }
+    });
+  }
+
+  let modeChangeEv;
+  await new TimedPromise(resolve => {
+    modeChangeEv = resolve;
+    win.addEventListener("sizemodechange", modeChangeEv, {once: true});
 
     if (win.windowState == win.STATE_MAXIMIZED) {
       win.restore();
     } else {
       win.maximize();
     }
-  });
+  }, {throws: null});
+  win.removeEventListener("sizemodechange", modeChangeEv);
+
+  // Transitioning into a window state is asynchronous on Linux, and we
+  // cannot rely on sizemodechange to accurately tell us when the
+  // transition has completed.
+  //
+  // To counter for this we wait for the window size to change, which
+  // it usually will.  On platforms where the transition is synchronous,
+  // the wait will have the cost of one iteration because the size will
+  // have changed as part of the transition.  Where the platform
+  // is asynchronous, the cost may be greater as we have to poll
+  // continuously until we see a change, but it ensures conformity in
+  // behaviour.
+  //
+  // Certain window managers, however, do not have a concept of maximised
+  // windows and here sizemodechange may never fire.  Indeed, if the
+  // window covers the maximum available screen real estate, the window
+  // size may also not change.  In this circumstance, which admittedly
+  // is a somewhat bizarre edge case, we assume that the timeout of
+  // waiting for sizemodechange to fire is sufficient to give the window
+  // enough time to transition itself into whatever form or shape the
+  // WM is programmed to give it.
+  await windowSizeChange();
 
   return this.curBrowser.rect;
 };
 
 /**
  * Synchronously sets the user agent window to full screen as if the user
  * had done "View > Enter Full Screen", or restores it if it is already
  * in full screen.