Commit e0905cc8 authored by YUKI "Piro" Hiroshi's avatar YUKI "Piro" Hiroshi
Browse files

Bug 1271047 - Place "popup" type window to given coordinates r=mixedpuppy

parent 40d44d6c
Loading
Loading
Loading
Loading
+71 −1
Original line number Diff line number Diff line
@@ -17,6 +17,69 @@ ChromeUtils.defineESModuleGetters(this, {

var { ExtensionError, promiseObserved } = ExtensionUtils;

function sanitizePositionParams(params, window = null, positionOffset = 0) {
  if (params.left === null && params.top === null) {
    return;
  }

  if (params.left === null) {
    const baseLeft = window ? window.screenX : 0;
    params.left = baseLeft + positionOffset;
  }
  if (params.top === null) {
    const baseTop = window ? window.screenY : 0;
    params.top = baseTop + positionOffset;
  }

  // boundary check: don't put window out of visible area
  const baseWidth = window ? window.outerWidth : 0;
  const baseHeight = window ? window.outerHeight : 0;
  // Secure minimum size of an window should be same to the one
  // defined at nsGlobalWindowOuter::CheckSecurityWidthAndHeight.
  const minWidth = 100;
  const minHeight = 100;
  const width = Math.max(
    minWidth,
    params.width !== null ? params.width : baseWidth
  );
  const height = Math.max(
    minHeight,
    params.height !== null ? params.height : baseHeight
  );
  const screenManager = Cc["@mozilla.org/gfx/screenmanager;1"].getService(
    Ci.nsIScreenManager
  );
  const screen = screenManager.screenForRect(
    params.left,
    params.top,
    width,
    height
  );
  const availDeviceLeft = {};
  const availDeviceTop = {};
  const availDeviceWidth = {};
  const availDeviceHeight = {};
  screen.GetAvailRect(
    availDeviceLeft,
    availDeviceTop,
    availDeviceWidth,
    availDeviceHeight
  );
  const factor = screen.defaultCSSScaleFactor;
  const availLeft = Math.floor(availDeviceLeft.value / factor);
  const availTop = Math.floor(availDeviceTop.value / factor);
  const availWidth = Math.floor(availDeviceWidth.value / factor);
  const availHeight = Math.floor(availDeviceHeight.value / factor);
  params.left = Math.min(
    availLeft + availWidth - width,
    Math.max(availLeft, params.left)
  );
  params.top = Math.min(
    availTop + availHeight - height,
    Math.max(availTop, params.top)
  );
}

this.windows = class extends ExtensionAPIPersistent {
  windowEventRegistrar(event, listener) {
    let { extension } = this;
@@ -325,10 +388,12 @@ this.windows = class extends ExtensionAPIPersistent {
              "dialog",
              "resizable",
              "minimizable",
              "centerscreen",
              "titlebar",
              "close"
            );
            if (createData.left === null && createData.top === null) {
              features.push("centerscreen");
            }
          }

          if (createData.incognito !== null) {
@@ -344,6 +409,10 @@ this.windows = class extends ExtensionAPIPersistent {
            }
          }

          const baseWindow = windowTracker.getTopNormalWindow(context);
          // 10px offset is same to Chromium
          sanitizePositionParams(createData, baseWindow, 10);

          let window = Services.ww.openWindow(
            null,
            AppConstants.BROWSER_CHROME_URL,
@@ -427,6 +496,7 @@ this.windows = class extends ExtensionAPIPersistent {
            win.window.getAttention();
          }

          sanitizePositionParams(updateInfo, win.window);
          win.updateGeometry(updateInfo);

          if (updateInfo.titlePreface !== null) {
+122 −0
Original line number Diff line number Diff line
@@ -124,3 +124,125 @@ add_task(async function testWindowCreateFocused() {

  ExtensionTestUtils.failOnSchemaWarnings(true);
});

add_task(async function testPopupTypeWithDimension() {
  let extension = ExtensionTestUtils.loadExtension({
    async background() {
      await browser.windows.create({
        type: "popup",
        left: 123,
        top: 123,
        width: 151,
        height: 152,
      });
      await browser.windows.create({
        type: "popup",
        left: 123,
        width: 152,
        height: 153,
      });
      await browser.windows.create({
        type: "popup",
        top: 123,
        width: 153,
        height: 154,
      });
      await browser.windows.create({
        type: "popup",
        left: screen.availWidth * 100,
        top: screen.availHeight * 100,
        width: 154,
        height: 155,
      });
      await browser.windows.create({
        type: "popup",
        left: -screen.availWidth * 100,
        top: -screen.availHeight * 100,
        width: 155,
        height: 156,
      });
      browser.test.sendMessage("windows-created");
    },
  });

  const baseWindow = await BrowserTestUtils.openNewBrowserWindow();
  baseWindow.resizeTo(150, 150);
  baseWindow.moveTo(50, 50);

  let windows = [];
  let windowListener = (window, topic) => {
    if (topic == "domwindowopened") {
      windows.push(window);
    }
  };
  Services.ww.registerNotification(windowListener);

  await extension.startup();
  await extension.awaitMessage("windows-created");
  await extension.unload();

  const regularScreen = getScreenAt(0, 0, 150, 150);
  const roundedX = roundCssPixcel(123, regularScreen);
  const roundedY = roundCssPixcel(123, regularScreen);

  const availRectLarge = getCssAvailRect(
    getScreenAt(screen.width * 100, screen.height * 100, 150, 150)
  );
  const maxRight = availRectLarge.right;
  const maxBottom = availRectLarge.bottom;

  const availRectSmall = getCssAvailRect(
    getScreenAt(-screen.width * 100, -screen.height * 100, 150, 150150)
  );
  const minLeft = availRectSmall.left;
  const minTop = availRectSmall.top;

  const actualCoordinates = windows
    .slice(0, 3)
    .map(window => `${window.screenX},${window.screenY}`);
  const offsetFromBase = 10;
  const expectedCoordinates = [
    `${roundedX},${roundedY}`,
    // Missing top should be +10 from the last browser window.
    `${roundedX},${baseWindow.screenY + offsetFromBase}`,
    // Missing left should be +10 from the last browser window.
    `${baseWindow.screenX + offsetFromBase},${roundedY}`,
  ];
  is(
    actualCoordinates.join(" / "),
    expectedCoordinates.join(" / "),
    "expected popup type windows are opened at given coordinates"
  );

  const actualSizes = windows
    .slice(0, 3)
    .map(window => `${window.outerWidth}x${window.outerHeight}`);
  const expectedSizes = [`151x152`, `152x153`, `153x154`];
  is(
    actualSizes.join(" / "),
    expectedSizes.join(" / "),
    "expected popup type windows are opened with given size"
  );

  const actualRect = {
    top: windows[4].screenY,
    bottom: windows[3].screenY + windows[3].outerHeight,
    left: windows[4].screenX,
    right: windows[3].screenX + windows[3].outerWidth,
  };
  const maxRect = {
    top: minTop,
    bottom: maxBottom,
    left: minLeft,
    right: maxRight,
  };
  isRectContained(actualRect, maxRect);

  for (const window of windows) {
    window.close();
  }

  Services.ww.unregisterNotification(windowListener);
  windows = null;
  await BrowserTestUtils.closeWindow(baseWindow);
});
+1 −1
Original line number Diff line number Diff line
@@ -84,7 +84,7 @@ add_task(async function testWindowCreate() {
        if (platformInfo.os != "linux") {
          geom = { left: -50, top: -50, width: 800, height: 600 };
          await browser.windows.update(windowId, geom);
          await checkWindow(geom);
          await checkWindow({ ...geom, left: 0, top: 0 });
        }

        await browser.windows.remove(windowId);
+133 −0
Original line number Diff line number Diff line
@@ -229,3 +229,136 @@ add_task(async function testWindowUpdateParams() {
  await extension.awaitFinish("window-update-params");
  await extension.unload();
});

add_task(async function testPositionBoundaryCheck() {
  const extension = ExtensionTestUtils.loadExtension({
    async background() {
      function waitMessage() {
        return new Promise((resolve, reject) => {
          const onMessage = message => {
            if (message == "continue") {
              browser.test.onMessage.removeListener(onMessage);
              resolve();
            }
          };
          browser.test.onMessage.addListener(onMessage);
        });
      }
      const win = await browser.windows.create({
        type: "popup",
        left: 50,
        top: 50,
        width: 150,
        height: 150,
      });
      await browser.test.sendMessage("ready");
      await waitMessage();
      await browser.windows.update(win.id, {
        left: 123,
        top: 123,
      });
      await browser.test.sendMessage("regular");
      await waitMessage();
      await browser.windows.update(win.id, {
        left: 123,
      });
      await browser.test.sendMessage("only-left");
      await waitMessage();
      await browser.windows.update(win.id, {
        top: 123,
      });
      await browser.test.sendMessage("only-top");
      await waitMessage();
      await browser.windows.update(win.id, {
        left: screen.availWidth * 100,
        top: screen.availHeight * 100,
      });
      await browser.test.sendMessage("too-large");
      await waitMessage();
      await browser.windows.update(win.id, {
        left: -screen.availWidth * 100,
        top: -screen.availHeight * 100,
      });
      await browser.test.sendMessage("too-small");
    },
  });

  const promisedWin = new Promise((resolve, reject) => {
    const windowListener = (window, topic) => {
      if (topic == "domwindowopened") {
        Services.ww.unregisterNotification(windowListener);
        resolve(window);
      }
    };
    Services.ww.registerNotification(windowListener);
  });

  await extension.startup();

  const win = await promisedWin;

  const regularScreen = getScreenAt(0, 0, 150, 150);
  const roundedX = roundCssPixcel(123, regularScreen);
  const roundedY = roundCssPixcel(123, regularScreen);

  const availRectLarge = getCssAvailRect(
    getScreenAt(screen.width * 100, screen.height * 100, 150, 150)
  );
  const maxRight = availRectLarge.right;
  const maxBottom = availRectLarge.bottom;

  const availRectSmall = getCssAvailRect(
    getScreenAt(-screen.width * 100, -screen.height * 100, 150, 150)
  );
  const minLeft = availRectSmall.left;
  const minTop = availRectSmall.top;

  const expectedCoordinates = [
    `${roundedX},${roundedY}`,
    `${roundedX},${win.screenY}`,
    `${win.screenX},${roundedY}`,
  ];

  await extension.awaitMessage("ready");

  const actualCoordinates = [];
  extension.sendMessage("continue");
  await extension.awaitMessage("regular");
  actualCoordinates.push(`${win.screenX},${win.screenY}`);
  win.moveTo(50, 50);
  extension.sendMessage("continue");
  await extension.awaitMessage("only-left");
  actualCoordinates.push(`${win.screenX},${win.screenY}`);
  win.moveTo(50, 50);
  extension.sendMessage("continue");
  await extension.awaitMessage("only-top");
  actualCoordinates.push(`${win.screenX},${win.screenY}`);
  is(
    actualCoordinates.join(" / "),
    expectedCoordinates.join(" / "),
    "expected window is placed at given coordinates"
  );

  const actualRect = {};
  const maxRect = {
    top: minTop,
    bottom: maxBottom,
    left: minLeft,
    right: maxRight,
  };

  extension.sendMessage("continue");
  await extension.awaitMessage("too-large");
  actualRect.right = win.screenX + win.outerWidth;
  actualRect.bottom = win.screenY + win.outerHeight;

  extension.sendMessage("continue");
  await extension.awaitMessage("too-small");
  actualRect.top = win.screenY;
  actualRect.left = win.screenX;

  isRectContained(actualRect, maxRect);

  await extension.unload();
  await BrowserTestUtils.closeWindow(win);
});
+51 −0
Original line number Diff line number Diff line
@@ -29,6 +29,7 @@
 *          navigateTab historyPushState promiseWindowRestored
 *          getIncognitoWindow startIncognitoMonitorExtension
 *          loadTestSubscript awaitBrowserLoaded backgroundColorSetOnRoot
 *          getScreenAt roundCssPixcel getCssAvailRect isRectContained
 */

// There are shutdown issues for which multiple rejections are left uncaught.
@@ -1016,3 +1017,53 @@ function backgroundColorSetOnRoot() {
  }
  return os.windowsVersion < 10;
}

function getScreenAt(left, top, width, height) {
  const screenManager = Cc["@mozilla.org/gfx/screenmanager;1"].getService(
    Ci.nsIScreenManager
  );
  return screenManager.screenForRect(left, top, width, height);
}

function roundCssPixcel(pixel, screen) {
  return Math.floor(
    Math.floor(pixel * screen.defaultCSSScaleFactor) /
      screen.defaultCSSScaleFactor
  );
}

function getCssAvailRect(screen) {
  const availDeviceLeft = {};
  const availDeviceTop = {};
  const availDeviceWidth = {};
  const availDeviceHeight = {};
  screen.GetAvailRect(
    availDeviceLeft,
    availDeviceTop,
    availDeviceWidth,
    availDeviceHeight
  );
  const factor = screen.defaultCSSScaleFactor;
  const left = Math.floor(availDeviceLeft.value / factor);
  const top = Math.floor(availDeviceTop.value / factor);
  const width = Math.floor(availDeviceWidth.value / factor);
  const height = Math.floor(availDeviceHeight.value / factor);
  return {
    left,
    top,
    width,
    height,
    right: left + width,
    bottom: top + height,
  };
}

function isRectContained(actualRect, maxRect) {
  is(
    `top=${actualRect.top >= maxRect.top},bottom=${actualRect.bottom <=
      maxRect.bottom},left=${actualRect.left >=
      maxRect.left},right=${actualRect.right <= maxRect.right}`,
    "top=true,bottom=true,left=true,right=true",
    `Dimension must be inside, top:${actualRect.top}>=${maxRect.top}, bottom:${actualRect.bottom}<=${maxRect.bottom}, left:${actualRect.left}>=${maxRect.left}, right:${actualRect.right}<=${maxRect.right}`
  );
}