Bug 1262332, fix select popup and invalid form popup position when inside a transformed frame, r=felipe

This commit is contained in:
Neil Deakin 2016-05-11 08:57:03 -04:00
Родитель 405358d4b3
Коммит bbc0cc12e6
3 изменённых файлов: 108 добавлений и 50 удалений

Просмотреть файл

@ -39,6 +39,14 @@ const PAGECONTENT_SMALL =
" <option value='Six'>Six</option>" +
"</select></body></html>";
const PAGECONTENT_TRANSLATED =
"<html><body>" +
"<div id='div'>" +
"<iframe id='frame' width='320' height='295' style='border: none;'" +
" src='data:text/html,<select id=select autofocus><option>he he he</option><option>boo boo</option><option>baz baz</option></select>'" +
"</iframe>" +
"</div></body></html>";
function openSelectPopup(selectPopup, withMouse, selector = "select")
{
let popupShownPromise = BrowserTestUtils.waitForEvent(selectPopup, "popupshown");
@ -48,7 +56,7 @@ function openSelectPopup(selectPopup, withMouse, selector = "select")
BrowserTestUtils.synthesizeMouseAtCenter(selector, { }, gBrowser.selectedBrowser)]);
}
setTimeout(() => EventUtils.synthesizeKey("KEY_ArrowDown", { altKey: true, code: "ArrowDown" }), 1500);
EventUtils.synthesizeKey("KEY_ArrowDown", { altKey: true, code: "ArrowDown" });
return popupShownPromise;
}
@ -202,3 +210,68 @@ add_task(function*() {
ok(true, "Popup hidden when tab is closed");
});
// This test opens a select popup that is isn't a frame and has some translations applied.
add_task(function*() {
const pageUrl = "data:text/html," + escape(PAGECONTENT_TRANSLATED);
let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, pageUrl);
let menulist = document.getElementById("ContentSelectDropdown");
let selectPopup = menulist.menupopup;
// First, get the position of the select popup when no translations have been applied.
yield openSelectPopup(selectPopup, false);
let rect = selectPopup.getBoundingClientRect();
let expectedX = rect.left;
let expectedY = rect.top;
yield hideSelectPopup(selectPopup);
// Iterate through a set of steps which each add more translation to the select's expected position.
let steps = [
[ "div", "transform: translateX(7px) translateY(13px);", 7, 13 ],
[ "frame", "border-top: 5px solid green; border-left: 10px solid red; border-right: 35px solid blue;", 10, 5 ],
[ "frame", "border: none; padding-left: 6px; padding-right: 12px; padding-top: 2px;", -4, -3 ],
[ "select", "margin: 9px; transform: translateY(-3px);", 9, 6 ],
];
for (let stepIndex = 0; stepIndex < steps.length; stepIndex++) {
let step = steps[stepIndex];
yield ContentTask.spawn(gBrowser.selectedBrowser, step, function*(step) {
return new Promise(resolve => {
let changedWin = content;
let elem;
if (step[0] == "select") {
changedWin = content.document.getElementById("frame").contentWindow;
elem = changedWin.document.getElementById("select");
}
else {
elem = content.document.getElementById(step[0]);
}
changedWin.addEventListener("MozAfterPaint", function onPaint() {
changedWin.removeEventListener("MozAfterPaint", onPaint);
resolve();
});
elem.style = step[1];
});
});
yield openSelectPopup(selectPopup, false);
expectedX += step[2];
expectedY += step[3];
let rect = selectPopup.getBoundingClientRect();
is(rect.left, expectedX, "step " + (stepIndex + 1) + " x");
is(rect.top, expectedY, "step " + (stepIndex + 1) + " y");
yield hideSelectPopup(selectPopup);
}
yield BrowserTestUtils.removeTab(tab);
});

Просмотреть файл

@ -190,7 +190,7 @@ FormSubmitObserver.prototype =
// Note, this is relative to the browser and needs to be translated
// in chrome.
panelData.contentRect = this._msgRect(aElement);
panelData.contentRect = BrowserUtils.getElementBoundingRect(aElement);
// We want to show the popup at the middle of checkbox and radio buttons
// and where the content begin for the other elements.
@ -232,21 +232,5 @@ FormSubmitObserver.prototype =
(target.ownerDocument && target.ownerDocument == this._content.document));
},
/*
* Return a message manager rect for the element's bounding client rect
* in top level browser coords.
*/
_msgRect: function (aElement) {
let domRect = aElement.getBoundingClientRect();
let zoomFactor = this._getWindowUtils().fullZoom;
let { offsetX, offsetY } = BrowserUtils.offsetToTopLevelWindow(this._content, aElement);
return {
left: (domRect.left + offsetX) * zoomFactor,
top: (domRect.top + offsetY) * zoomFactor,
width: domRect.width * zoomFactor,
height: domRect.height * zoomFactor
};
},
QueryInterface : XPCOMUtils.generateQI([Ci.nsIFormSubmitObserver])
};

Просмотреть файл

@ -108,15 +108,44 @@ this.BrowserUtils = {
* the coordinates are relative to the user's screen.
*/
getElementBoundingScreenRect: function(aElement) {
return this.getElementBoundingRect(aElement, true);
},
/**
* For a given DOM element, returns its position as an offset from the topmost
* window. In a content process, the coordinates returned will be relative to
* the left/top of the topmost content area. If aInScreenCoords is true,
* screen coordinates will be returned instead.
*/
getElementBoundingRect: function(aElement, aInScreenCoords) {
let rect = aElement.getBoundingClientRect();
let window = aElement.ownerDocument.defaultView;
let win = aElement.ownerDocument.defaultView;
let x = rect.left, y = rect.top;
// We need to compensate for any iframes that might shift things
// over. We also need to compensate for zooming.
let fullZoom = window.getInterface(Ci.nsIDOMWindowUtils).fullZoom;
let parentFrame = win.frameElement;
while (parentFrame) {
win = parentFrame.ownerDocument.defaultView;
let cstyle = win.getComputedStyle(parentFrame, "");
let framerect = parentFrame.getBoundingClientRect();
x += framerect.left + parseFloat(cstyle.borderLeftWidth) + parseFloat(cstyle.paddingLeft);
y += framerect.top + parseFloat(cstyle.borderTopWidth) + parseFloat(cstyle.paddingTop);
parentFrame = win.frameElement;
}
if (aInScreenCoords) {
x += win.mozInnerScreenX;
y += win.mozInnerScreenY;
}
let fullZoom = win.getInterface(Ci.nsIDOMWindowUtils).fullZoom;
rect = {
left: (rect.left + window.mozInnerScreenX) * fullZoom,
top: (rect.top + window.mozInnerScreenY) * fullZoom,
left: x * fullZoom,
top: y * fullZoom,
width: rect.width * fullZoom,
height: rect.height * fullZoom
};
@ -124,34 +153,6 @@ this.BrowserUtils = {
return rect;
},
/**
* Given an element potentially within a subframe, calculate the offsets
* up to the top level browser.
*
* @param aTopLevelWindow content window to calculate offsets to.
* @param aElement The element in question.
* @return [targetWindow, offsetX, offsetY]
*/
offsetToTopLevelWindow: function (aTopLevelWindow, aElement) {
let offsetX = 0;
let offsetY = 0;
let element = aElement;
while (element &&
element.ownerDocument &&
element.ownerDocument.defaultView != aTopLevelWindow) {
element = element.ownerDocument.defaultView.frameElement;
let rect = element.getBoundingClientRect();
offsetX += rect.left;
offsetY += rect.top;
}
let win = null;
if (element == aElement)
win = aTopLevelWindow;
else
win = element.contentDocument.defaultView;
return { targetWindow: win, offsetX: offsetX, offsetY: offsetY };
},
onBeforeLinkTraversal: function(originalTarget, linkURI, linkNode, isAppTab) {
// Don't modify non-default targets or targets that aren't in top-level app
// tab docshells (isAppTab will be false for app tab subframes).