Bug 1271047 - Place "popup" type window to given coordinates r=mixedpuppy
Differential Revision: https://phabricator.services.mozilla.com/D73419
This commit is contained in:
@@ -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) {
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
|
||||
@@ -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.
|
||||
@@ -1011,3 +1012,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}`
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user