Bug 1253133: [webext] Support changing window geometry via windows.update. r=aswan
authorKris Maglione <maglione.k@gmail.com>
Tue, 22 Mar 2016 23:13:11 +0100
changeset 289913 3ad9e7d55c6e393323c4afb57d4ce32e8d8a89cc
parent 289912 1f4e567893feb5e288621c50d58e99232a141c48
child 289914 fbd4c4f1bf4feb810f6a03c39499995610ffb190
push id19656
push usergwagner@mozilla.com
push dateMon, 04 Apr 2016 13:43:23 +0000
treeherderb2g-inbound@e99061fde28a [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersaswan
bugs1253133
milestone48.0a1
Bug 1253133: [webext] Support changing window geometry via windows.update. r=aswan MozReview-Commit-ID: LQGXyB9WuiI
browser/components/extensions/ext-utils.js
browser/components/extensions/ext-windows.js
browser/components/extensions/schemas/windows.json
browser/components/extensions/test/browser/browser.ini
browser/components/extensions/test/browser/browser_ext_windows_size.js
browser/components/extensions/test/browser/browser_ext_windows_update.js
--- a/browser/components/extensions/ext-utils.js
+++ b/browser/components/extensions/ext-utils.js
@@ -661,16 +661,30 @@ global.WindowManager = {
 
     if (chromeFlags & Ci.nsIWebBrowserChrome.CHROME_OPENAS_DIALOG) {
       return "popup";
     }
 
     return "normal";
   },
 
+  updateGeometry(window, options) {
+    if (options.left !== null || options.top !== null) {
+      let left = options.left !== null ? options.left : window.screenX;
+      let top = options.top !== null ? options.top : window.screenY;
+      window.moveTo(left, top);
+    }
+
+    if (options.width !== null || options.height !== null) {
+      let width = options.width !== null ? options.width : window.outerWidth;
+      let height = options.height !== null ? options.height : window.outerHeight;
+      window.resizeTo(width, height);
+    }
+  },
+
   getId(window) {
     if (this._windows.has(window)) {
       return this._windows.get(window);
     }
     let id = this._nextId++;
     this._windows.set(window, id);
     return id;
   },
--- a/browser/components/extensions/ext-windows.js
+++ b/browser/components/extensions/ext-windows.js
@@ -60,21 +60,24 @@ extensions.registerSchemaAPI("windows", 
 
       getAll: function(getInfo) {
         let windows = Array.from(WindowListManager.browserWindows(),
                                  window => WindowManager.convert(extension, window, getInfo));
         return Promise.resolve(windows);
       },
 
       create: function(createData) {
-        if (createData.state !== null && createData.state != "normal") {
-          if (createData.left !== null || createData.top !== null ||
-              createData.width !== null || createData.height !== null) {
+        let needResize = (createData.left !== null || createData.top !== null ||
+                          createData.width !== null || createData.height !== null);
+
+        if (needResize) {
+          if (createData.state !== null && createData.state != "normal") {
             return Promise.reject({message: `"state": "${createData.state}" may not be combined with "left", "top", "width", or "height"`});
           }
+          createData.state = "normal";
         }
 
         function mkstr(s) {
           let result = Cc["@mozilla.org/supports-string;1"].createInstance(Ci.nsISupportsString);
           result.data = s;
           return result;
         }
 
@@ -128,26 +131,17 @@ extensions.registerSchemaAPI("windows", 
           } else {
             features.push("non-private");
           }
         }
 
         let window = Services.ww.openWindow(null, "chrome://browser/content/browser.xul", "_blank",
                                             features.join(","), args);
 
-        if (createData.left !== null || createData.top !== null) {
-          let left = createData.left !== null ? createData.left : window.screenX;
-          let top = createData.top !== null ? createData.top : window.screenY;
-          window.moveTo(left, top);
-        }
-        if (createData.width !== null || createData.height !== null) {
-          let width = createData.width !== null ? createData.width : window.outerWidth;
-          let height = createData.height !== null ? createData.height : window.outerHeight;
-          window.resizeTo(width, height);
-        }
+        WindowManager.updateGeometry(window, createData);
 
         // TODO: focused, type
 
         return new Promise(resolve => {
           window.addEventListener("load", function listener() {
             window.removeEventListener("load", listener);
 
             if (createData.state == "maximized" || createData.state == "normal" ||
@@ -171,38 +165,39 @@ extensions.registerSchemaAPI("windows", 
             resolve();
           });
         }).then(() => {
           return WindowManager.convert(extension, window);
         });
       },
 
       update: function(windowId, updateInfo) {
-        // TODO: When we support size/position updates:
-        // if (updateInfo.state !== null && updateInfo.state != "normal") {
-        //   if (updateInfo.left !== null || updateInfo.top !== null ||
-        //       updateInfo.width !== null || updateInfo.height !== null) {
-        //     return Promise.reject({message: `"state": "${updateInfo.state}" may not be combined with "left", "top", "width", or "height"`});
-        //   }
-        // }
+        if (updateInfo.state !== null && updateInfo.state != "normal") {
+          if (updateInfo.left !== null || updateInfo.top !== null ||
+              updateInfo.width !== null || updateInfo.height !== null) {
+            return Promise.reject({message: `"state": "${updateInfo.state}" may not be combined with "left", "top", "width", or "height"`});
+          }
+        }
 
         let window = WindowManager.getWindow(windowId, context);
         if (updateInfo.focused) {
           Services.focus.activeWindow = window;
         }
 
         if (updateInfo.state !== null) {
           WindowManager.setState(window, updateInfo.state);
         }
 
         if (updateInfo.drawAttention) {
           // Bug 1257497 - Firefox can't cancel attention actions.
           window.getAttention();
         }
 
+        WindowManager.updateGeometry(window, updateInfo);
+
         // TODO: All the other properties, focused=false...
 
         return Promise.resolve(WindowManager.convert(extension, window));
       },
 
       remove: function(windowId) {
         let window = WindowManager.getWindow(windowId, context);
         window.close();
--- a/browser/components/extensions/schemas/windows.json
+++ b/browser/components/extensions/schemas/windows.json
@@ -372,36 +372,32 @@
             "name": "windowId",
             "minimum": -2
           },
           {
             "type": "object",
             "name": "updateInfo",
             "properties": {
               "left": {
-                "unsupported": true,
                 "type": "integer",
                 "optional": true,
                 "description": "The offset from the left edge of the screen to move the window to in pixels. This value is ignored for panels."
               },
               "top": {
-                "unsupported": true,
                 "type": "integer",
                 "optional": true,
                 "description": "The offset from the top edge of the screen to move the window to in pixels. This value is ignored for panels."
               },
               "width": {
-                "unsupported": true,
                 "type": "integer",
                 "minimum": 0,
                 "optional": true,
                 "description": "The width to resize the window to in pixels. This value is ignored for panels."
               },
               "height": {
-                "unsupported": true,
                 "type": "integer",
                 "minimum": 0,
                 "optional": true,
                 "description": "The height to resize the window to in pixels. This value is ignored for panels."
               },
               "focused": {
                 "type": "boolean",
                 "optional": true,
--- a/browser/components/extensions/test/browser/browser.ini
+++ b/browser/components/extensions/test/browser/browser.ini
@@ -52,14 +52,16 @@ support-files =
 [browser_ext_tabs_move_window.js]
 [browser_ext_tabs_move_window_multiple.js]
 [browser_ext_tabs_move_window_pinned.js]
 [browser_ext_tabs_onHighlighted.js]
 [browser_ext_windows_create.js]
 tags = fullscreen
 [browser_ext_windows_create_tabId.js]
 [browser_ext_windows.js]
+[browser_ext_windows_size.js]
+skip-if = os == 'mac' # Fails when windows are randomly opened in fullscreen mode
 [browser_ext_windows_update.js]
 tags = fullscreen
 [browser_ext_contentscript_connect.js]
 [browser_ext_tab_runtimeConnect.js]
 [browser_ext_topwindowid.js]
 [browser_ext_webNavigation_getFrames.js]
new file mode 100644
--- /dev/null
+++ b/browser/components/extensions/test/browser/browser_ext_windows_size.js
@@ -0,0 +1,114 @@
+/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set sts=2 sw=2 et tw=80: */
+"use strict";
+
+add_task(function* testWindowCreate() {
+  let extension = ExtensionTestUtils.loadExtension({
+    background() {
+      let _checkWindowPromise;
+      browser.test.onMessage.addListener((msg, arg) => {
+        if (msg == "checked-window") {
+          _checkWindowPromise.resolve(arg);
+          _checkWindowPromise = null;
+        }
+      });
+
+      let getWindowSize = () => {
+        return new Promise(resolve => {
+          _checkWindowPromise = {resolve};
+          browser.test.sendMessage("check-window");
+        });
+      };
+
+      const KEYS = ["left", "top", "width", "height"];
+      function checkGeom(expected, actual) {
+        for (let key of KEYS) {
+          browser.test.assertEq(expected[key], actual[key], `Expected '${key}' value`);
+        }
+      }
+
+      let windowId;
+      function checkWindow(expected, retries = 5) {
+        return getWindowSize().then(geom => {
+          if (retries && KEYS.some(key => expected[key] != geom[key])) {
+            browser.test.log(`Got mismatched size (${JSON.stringify(expected)} != ${JSON.stringify(geom)}). ` +
+                             `Retrying after a short delay.`);
+
+            return new Promise(resolve => {
+              setTimeout(resolve, 200);
+            }).then(() => {
+              return checkWindow(expected, retries - 1);
+            });
+          }
+
+          browser.test.log(`Check actual window size`);
+          checkGeom(expected, geom);
+
+          browser.test.log("Check API-reported window size");
+          return browser.windows.get(windowId).then(geom => {
+            checkGeom(expected, geom);
+          });
+        });
+      }
+
+      let geom = {left: 100, top: 100, width: 500, height: 300};
+
+      return browser.windows.create(geom).then(window => {
+        windowId = window.id;
+
+        return checkWindow(geom);
+      }).then(() => {
+        let update = {left: 150, width: 600};
+        Object.assign(geom, update);
+
+        return browser.windows.update(windowId, update);
+      }).then(() => {
+        return checkWindow(geom);
+      }).then(() => {
+        let update = {top: 150, height: 400};
+        Object.assign(geom, update);
+
+        return browser.windows.update(windowId, update);
+      }).then(() => {
+        return checkWindow(geom);
+      }).then(() => {
+        geom = {left: 200, top: 200, width: 800, height: 600};
+
+        return browser.windows.update(windowId, geom);
+      }).then(() => {
+        return checkWindow(geom);
+      }).then(() => {
+        return browser.windows.remove(windowId);
+      }).then(() => {
+        browser.test.notifyPass("window-size");
+      }).catch(e => {
+        browser.test.fail(`${e} :: ${e.stack}`);
+        browser.test.notifyFail("window-size");
+      });
+    },
+  });
+
+  let latestWindow;
+  let windowListener = (window, topic) => {
+    if (topic == "domwindowopened") {
+      latestWindow = window;
+    }
+  };
+  Services.ww.registerNotification(windowListener);
+
+  extension.onMessage("check-window", () => {
+    extension.sendMessage("checked-window", {
+      top: latestWindow.screenY,
+      left: latestWindow.screenX,
+      width: latestWindow.outerWidth,
+      height: latestWindow.outerHeight,
+    });
+  });
+
+  yield extension.startup();
+  yield extension.awaitFinish("window-size");
+  yield extension.unload();
+
+  Services.ww.unregisterNotification(windowListener);
+  latestWindow = null;
+});
--- a/browser/components/extensions/test/browser/browser_ext_windows_update.js
+++ b/browser/components/extensions/test/browser/browser_ext_windows_update.js
@@ -143,8 +143,46 @@ add_task(function* () {
   });
 
   yield Promise.all([extension.startup(), extension.awaitMessage("check")]);
 
   yield extension.unload();
 
   yield BrowserTestUtils.closeWindow(window2);
 });
+
+
+// Tests that incompatible parameters can't be used together.
+add_task(function* testWindowUpdateParams() {
+  let extension = ExtensionTestUtils.loadExtension({
+    background() {
+      function* getCalls() {
+        for (let state of ["minimized", "maximized", "fullscreen"]) {
+          for (let param of ["left", "top", "width", "height"]) {
+            let expected = `"state": "${state}" may not be combined with "left", "top", "width", or "height"`;
+
+            let windowId = browser.windows.WINDOW_ID_CURRENT;
+            yield browser.windows.update(windowId, {state, [param]: 100}).then(
+              val => {
+                browser.test.fail(`Expected error but got "${val}" instead`);
+              },
+              error => {
+                browser.test.assertTrue(
+                  error.message.includes(expected),
+                  `Got expected error (got: '${error.message}', expected: '${expected}'`);
+              });
+          }
+        }
+      }
+
+      Promise.all(getCalls()).then(() => {
+        browser.test.notifyPass("window-update-params");
+      }).catch(e => {
+        browser.test.fail(`${e} :: ${e.stack}`);
+        browser.test.notifyFail("window-update-params");
+      });
+    },
+  });
+
+  yield extension.startup();
+  yield extension.awaitFinish("window-update-params");
+  yield extension.unload();
+});