Bug 1253133: [webext] Support changing window geometry via windows.update. r?aswan draft
authorKris Maglione <maglione.k@gmail.com>
Thu, 17 Mar 2016 18:22:18 +0100
changeset 341695 8623ab528abdc5712923056be3ed8dac09a1c931
parent 341694 da1f0def67986b6b7200556c8b1a9e824df39b7a
child 516443 ec640035b9a2b451683313b527ca83ab378e5650
push id13269
push usermaglione.k@gmail.com
push dateThu, 17 Mar 2016 17:23:36 +0000
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
@@ -128,26 +128,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,33 +162,34 @@ 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);
         }
 
+        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,15 @@ 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]
 [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,99 @@
+/* -*- 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 windowId;
+
+      function checkWindow(expected) {
+        return new Promise(resolve => {
+          _checkWindowPromise = {resolve};
+          browser.test.sendMessage("check-window", expected);
+        }).then(geom => {
+          browser.test.log("Check actual window size");
+
+          for (let key of ["left", "top", "width", "height"]) {
+            browser.test.assertEq(expected[key], geom[key], `Expected '${key}' value`);
+          }
+
+          return browser.windows.get(windowId);
+        }).then(geom => {
+          browser.test.log("Check API-reported window size");
+
+          for (let key of ["left", "top", "width", "height"]) {
+            browser.test.assertEq(expected[key], geom[key], `Expected '${key}' value`);
+          }
+        });
+      }
+
+      let geom = {left: 100, top: 100, width: 500, height: 100};
+
+      return browser.windows.create(geom).then(window => {
+        windowId = window.id;
+
+        return checkWindow(geom);
+      }).then(() => {
+        let update = {left: 50, width: 600};
+        Object.assign(geom, update);
+
+        return browser.windows.update(windowId, update);
+      }).then(() => {
+        return checkWindow(geom);
+      }).then(() => {
+        let update = {top: 50, height: 200};
+        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", expected => {
+    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
@@ -120,8 +120,47 @@ add_task(function* testWindowUpdate() {
 
     extension.sendMessage("checked-window");
   });
 
   yield extension.startup();
   yield extension.awaitFinish("window-update");
   yield extension.unload();
 });
+
+
+// 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();
+});
+