Bug 1684792, open form validation popup anchored at screen coordinate as datetime picker and select do so that it is positioned correctly in out of process iframes, r=Gijs

Differential Revision: https://phabricator.services.mozilla.com/D100803
This commit is contained in:
Neil Deakin 2021-01-11 20:11:41 +00:00
Родитель e01e74c71e
Коммит 094e640230
4 изменённых файлов: 66 добавлений и 47 удалений

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

@ -154,30 +154,16 @@ class FormValidationChild extends JSWindowActorChild {
panelData.message = this._validationMessage;
// Note, this is relative to the browser and needs to be translated
// in chrome.
panelData.contentRect = BrowserUtils.getElementBoundingRect(aElement);
panelData.screenRect = BrowserUtils.getElementBoundingScreenRect(aElement);
// We want to show the popup at the middle of checkbox and radio buttons
// and where the content begin for the other elements.
let offset = 0;
if (
aElement.tagName == "INPUT" &&
(aElement.type == "radio" || aElement.type == "checkbox")
) {
panelData.position = "bottomcenter topleft";
} else {
let win = aElement.ownerGlobal;
let style = win.getComputedStyle(aElement);
if (style.direction == "rtl") {
offset =
parseInt(style.paddingRight) + parseInt(style.borderRightWidth);
} else {
offset = parseInt(style.paddingLeft) + parseInt(style.borderLeftWidth);
}
let zoomFactor = this.contentWindow.windowUtils.fullZoom;
panelData.offset = Math.round(offset * zoomFactor);
panelData.position = "after_start";
}
this.sendAsyncMessage("FormValidation:ShowPopup", panelData);

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

@ -96,9 +96,7 @@ class FormValidationParent extends JSWindowActorParent {
*
* @aPanelData - Object that contains popup information
* aPanelData stucture detail:
* contentRect - the bounding client rect of the target element. If
* content is remote, this is relative to the browser, otherwise its
* relative to the window.
* screenRect - the screen rect of the target element.
* position - popup positional string constants.
* message - the form element validation message text.
*/
@ -112,10 +110,6 @@ class FormValidationParent extends JSWindowActorParent {
let tabBrowser = window.gBrowser;
this._anchor = tabBrowser.selectedBrowser.popupAnchor;
this._anchor.style.left = aPanelData.contentRect.left + "px";
this._anchor.style.top = aPanelData.contentRect.top + "px";
this._anchor.style.width = aPanelData.contentRect.width + "px";
this._anchor.style.height = aPanelData.contentRect.height + "px";
this._anchor.hidden = false;
// Display the panel if it isn't already visible.
@ -129,7 +123,16 @@ class FormValidationParent extends JSWindowActorParent {
tabBrowser.selectedBrowser.addEventListener("TextZoomChange", this);
// Open the popup
this._panel.openPopup(this._anchor, aPanelData.position, 0, 0, false);
let rect = aPanelData.screenRect;
this._panel.openPopupAtScreenRect(
aPanelData.position,
rect.left,
rect.top,
rect.width,
rect.height,
false,
false
);
}
}

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

@ -43,11 +43,22 @@ ok(
"The browser should have a popup to show when a form is invalid"
);
function checkPopupShow() {
function isWithinHalfPixel(a, b) {
return Math.abs(a - b) <= 0.5;
}
function checkPopupShow(anchorRect) {
ok(
gInvalidFormPopup.state == "showing" || gInvalidFormPopup.state == "open",
"[Test " + testId + "] The invalid form popup should be shown"
);
// Just check the vertical position, as the horizontal position of an
// arrow panel will be offset.
is(
isWithinHalfPixel(gInvalidFormPopup.screenY),
isWithinHalfPixel(anchorRect.bottom),
"popup top"
);
}
function checkPopupHide() {
@ -95,9 +106,13 @@ async function openNewTab(uri, background) {
return browser;
}
async function clickChildElement(browser) {
await SpecialPowers.spawn(browser, [], async function() {
content.document.getElementById("s").click();
function clickChildElement(browser) {
return SpecialPowers.spawn(browser, [], async function() {
let element = content.document.getElementById("s");
element.click();
return {
bottom: content.mozInnerScreenY + element.getBoundingClientRect().bottom,
};
});
}
@ -171,10 +186,11 @@ add_task(async function() {
gInvalidFormPopup,
"popupshown"
);
await clickChildElement(browser);
let anchorRect = await clickChildElement(browser);
await popupShownPromise;
checkPopupShow();
checkPopupShow(anchorRect);
await checkChildFocus(
browser,
gInvalidFormPopup.firstElementChild.textContent
@ -199,10 +215,10 @@ add_task(async function() {
gInvalidFormPopup,
"popupshown"
);
await clickChildElement(browser);
let anchorRect = await clickChildElement(browser);
await popupShownPromise;
checkPopupShow();
checkPopupShow(anchorRect);
await checkChildFocus(
browser,
gInvalidFormPopup.firstElementChild.textContent
@ -227,10 +243,10 @@ add_task(async function() {
gInvalidFormPopup,
"popupshown"
);
await clickChildElement(browser);
let anchorRect = await clickChildElement(browser);
await popupShownPromise;
checkPopupShow();
checkPopupShow(anchorRect);
await checkChildFocus(
browser,
gInvalidFormPopup.firstElementChild.textContent
@ -262,10 +278,10 @@ add_task(async function() {
gInvalidFormPopup,
"popupshown"
);
await clickChildElement(browser);
let anchorRect = await clickChildElement(browser);
await popupShownPromise;
checkPopupShow();
checkPopupShow(anchorRect);
await checkChildFocus(
browser,
gInvalidFormPopup.firstElementChild.textContent
@ -274,7 +290,7 @@ add_task(async function() {
await new Promise((resolve, reject) => {
EventUtils.sendString("a");
executeSoon(function() {
checkPopupShow();
checkPopupShow(anchorRect);
resolve();
});
});
@ -298,10 +314,10 @@ add_task(async function() {
gInvalidFormPopup,
"popupshown"
);
await clickChildElement(browser);
let anchorRect = await clickChildElement(browser);
await popupShownPromise;
checkPopupShow();
checkPopupShow(anchorRect);
await checkChildFocus(
browser,
gInvalidFormPopup.firstElementChild.textContent
@ -332,10 +348,10 @@ add_task(async function() {
gInvalidFormPopup,
"popupshown"
);
await clickChildElement(browser);
let anchorRect = await clickChildElement(browser);
await popupShownPromise;
checkPopupShow();
checkPopupShow(anchorRect);
await checkChildFocus(
browser,
gInvalidFormPopup.firstElementChild.textContent
@ -366,10 +382,10 @@ add_task(async function() {
gInvalidFormPopup,
"popupshown"
);
await clickChildElement(browser1);
let anchorRect = await clickChildElement(browser1);
await popupShownPromise;
checkPopupShow();
checkPopupShow(anchorRect);
await checkChildFocus(
browser1,
gInvalidFormPopup.firstElementChild.textContent
@ -403,10 +419,10 @@ add_task(async function() {
gInvalidFormPopup,
"popupshown"
);
await clickChildElement(browser);
let anchorRect = await clickChildElement(browser);
await popupShownPromise;
checkPopupShow();
checkPopupShow(anchorRect);
await checkChildFocus(
browser,
gInvalidFormPopup.firstElementChild.textContent
@ -486,10 +502,10 @@ add_task(async function() {
gInvalidFormPopup,
"popupshown"
);
await clickChildElement(browser);
let anchorRect = await clickChildElement(browser);
await popupShownPromise;
checkPopupShow();
checkPopupShow(anchorRect);
await checkChildFocus(
browser,
gInvalidFormPopup.firstElementChild.textContent

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

@ -30,7 +30,7 @@ add_task(async function test_iframe() {
});
await popupShownPromise;
await SpecialPowers.spawn(browser, [], async function() {
let anchorBottom = await SpecialPowers.spawn(browser, [], async function() {
let childdoc = content.document.getElementsByTagName("iframe")[0]
.contentDocument;
Assert.equal(
@ -38,8 +38,22 @@ add_task(async function test_iframe() {
childdoc.getElementById("i"),
"First invalid element should be focused"
);
return (
childdoc.defaultView.mozInnerScreenY +
childdoc.getElementById("i").getBoundingClientRect().bottom
);
});
function isWithinHalfPixel(a, b) {
return Math.abs(a - b) <= 0.5;
}
is(
isWithinHalfPixel(gInvalidFormPopup.screenY),
isWithinHalfPixel(anchorBottom),
"popup top"
);
ok(
gInvalidFormPopup.state == "showing" || gInvalidFormPopup.state == "open",
"The invalid form popup should be shown"