Bug 1294413 - Bounding client validation popup to selected browser.
MozReview-Commit-ID: 5cnAV9MTGRY
--- a/browser/modules/FormValidationHandler.jsm
+++ b/browser/modules/FormValidationHandler.jsm
@@ -36,22 +36,17 @@ var FormValidationHandler =
// Listeners are added in nsBrowserGlue.js
receiveMessage(aMessage) {
let window = aMessage.target.ownerGlobal;
let json = aMessage.json;
let tabBrowser = window.gBrowser;
switch (aMessage.name) {
case "FormValidation:ShowPopup":
- // target is the <browser>, make sure we're receiving a message
- // from the foreground tab.
- if (tabBrowser && aMessage.target != tabBrowser.selectedBrowser) {
- return;
- }
- this._showPopup(window, json);
+ this._showPopup(aMessage.target, json);
break;
case "FormValidation:HidePopup":
this._hidePopup();
break;
}
},
observe(aSubject, aTopic, aData) {
@@ -89,39 +84,70 @@ var FormValidationHandler =
this._anchor.hidden = true;
this._anchor = null;
},
/*
* Shows the form validation popup at a specified position or updates the
* messaging and position if the popup is already displayed.
*
- * @aWindow - the chrome window
+ * @aBrowser - the browser instance
* @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.
* position - popup positional string constants.
* message - the form element validation message text.
*/
- _showPopup(aWindow, aPanelData) {
- let previouslyShown = !!this._panel;
- this._panel = aWindow.document.getElementById("invalid-form-popup");
- this._panel.firstChild.textContent = aPanelData.message;
- this._panel.hidden = false;
+ _showPopup(aBrowser, aPanelData) {
+ let window = aBrowser.ownerGlobal;
+ let tabBrowser = window.gBrowser;
+
+ /* first ignore validation messages from non visible forms */
+ if (Services.focus.activeWindow != window ||
+ tabBrowser.selectedBrowser != aBrowser) {
+ return;
+ }
- let tabBrowser = aWindow.gBrowser;
+ let winUtils = window.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIDOMWindowUtils);
+ let docRect = winUtils.getBoundsWithoutFlushing(tabBrowser.selectedBrowser);
+ /*
+ Instead of using the cache of the anchor element winUtils.getBoundsWithoutFlushing(this._anchor);
+ as the anchors parent is the document we can calc the rect from the position
+ */
+ let anchorRect = {};
+ anchorRect.left = docRect.left + aPanelData.contentRect.left;
+ anchorRect.top = docRect.top + aPanelData.contentRect.top;
+ anchorRect.bottom = anchorRect.top + aPanelData.contentRect.height;
+ anchorRect.right = anchorRect.left + aPanelData.contentRect.width;
+
+ /* if the anchor is outside of the document don't display */
+ if (!(anchorRect.left >= docRect.left &&
+ anchorRect.right <= docRect.right &&
+ anchorRect.top >= docRect.top &&
+ anchorRect.bottom <= docRect.bottom)) {
+ // As the page might have scrolled from the previously calculated placement, we should hide the popup if showing
+ this._hidePopup();
+ return;
+ }
+
this._anchor = tabBrowser.popupAnchor;
this._anchor.left = aPanelData.contentRect.left;
this._anchor.top = aPanelData.contentRect.top;
this._anchor.width = aPanelData.contentRect.width;
this._anchor.height = aPanelData.contentRect.height;
this._anchor.hidden = false;
+ let previouslyShown = !!this._panel;
+ this._panel = window.document.getElementById("invalid-form-popup");
+ this._panel.firstChild.textContent = aPanelData.message;
+ this._panel.hidden = false;
+
// Display the panel if it isn't already visible.
if (!previouslyShown) {
// Cleanup after the popup is hidden
this._panel.addEventListener("popuphiding", this, true);
// Hide if the user scrolls the page
tabBrowser.selectedBrowser.addEventListener("scroll", this, true);
tabBrowser.selectedBrowser.addEventListener("FullZoomChange", this);
--- a/dom/html/test/browser.ini
+++ b/dom/html/test/browser.ini
@@ -27,9 +27,10 @@ support-files =
[browser_form_post_from_file_to_http.js]
[browser_fullscreen-api-keys.js]
tags = fullscreen
[browser_fullscreen-contextmenu-esc.js]
tags = fullscreen
[browser_submission_flush.js]
[browser_refresh_wyciwyg_url.js]
support-files =
- file_refresh_wyciwyg_url.html
\ No newline at end of file
+ file_refresh_wyciwyg_url.html
+[browser_prevent_error_spoofing.js]
new file mode 100644
--- /dev/null
+++ b/dom/html/test/browser_prevent_error_spoofing.js
@@ -0,0 +1,38 @@
+let testId = 0;
+const gInvalidFormPopup = document.getElementById("invalid-form-popup");
+function checkPopupShow() {
+ ok(gInvalidFormPopup.state == "showing" || gInvalidFormPopup.state == "open",
+ "[Test " + testId + "] The invalid form popup should be shown");
+}
+
+function checkPopupHide() {
+ ok(gInvalidFormPopup.state != "showing" && gInvalidFormPopup.state != "open",
+ "[Test " + testId + "] The invalid form popup should not be shown");
+}
+
+add_task(async function test_error_loads() {
+ const dataUrl = "data:text/html;charset=utf-8,";
+ const testPage = `${dataUrl}
+ <form>
+ <input id="qaz" required="" x-moz-errormessage="I am a spoof" type="text">
+ <input id="q" value="Submit" type="submit">
+ </form>
+ <style>
+ #qaz{
+ position: absolute;
+ border: 1px solid red;
+ top: -74px;
+ left: 95px;
+ }
+ #q{
+ position: absolute;
+ top: -100px;
+ }
+ </style>`;
+
+ let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, testPage);
+ checkPopupHide();
+ await BrowserTestUtils.synthesizeMouse("#q", 0, 0, {}, gBrowser.selectedBrowser);
+ checkPopupHide();
+ BrowserTestUtils.removeTab(tab);
+});
--- a/toolkit/modules/DateTimePickerHelper.jsm
+++ b/toolkit/modules/DateTimePickerHelper.jsm
@@ -50,21 +50,17 @@ var DateTimePickerHelper = {
receiveMessage(aMessage) {
debug("receiveMessage: " + aMessage.name);
switch (aMessage.name) {
case "FormDateTime:OpenPicker": {
this.showPicker(aMessage.target, aMessage.data);
break;
}
case "FormDateTime:ClosePicker": {
- if (!this.picker) {
- return;
- }
- this.picker.closePicker();
- this.close();
+ this._closePicker();
break;
}
case "FormDateTime:UpdatePicker": {
if (!this.picker) {
return;
}
this.picker.setPopupValue(aMessage.data);
break;
@@ -79,61 +75,99 @@ var DateTimePickerHelper = {
debug("handleEvent: " + aEvent.type);
switch (aEvent.type) {
case "DateTimePickerValueChanged": {
this.updateInputBoxValue(aEvent);
break;
}
case "popuphidden": {
let browser = this.weakBrowser ? this.weakBrowser.get() : null;
- if (browser) {
- browser.messageManager.sendAsyncMessage("FormDateTime:PickerClosed");
- }
- this.picker.closePicker();
- this.close();
+ this._sendCloseEvent(browser);
+ this._closePicker();
break;
}
default:
break;
}
},
+ // Close UI and cleanup
+ _closePicker() {
+ if (!this.picker) {
+ return;
+ }
+ this.picker.closePicker();
+ this.close();
+ },
+
+ // Send close event to browser-content.js
+ _sendCloseEvent(browser) {
+ if (browser) {
+ browser.messageManager.sendAsyncMessage("FormDateTime:PickerClosed");
+ }
+ },
+
// Called when picker value has changed, notify input box about it.
updateInputBoxValue(aEvent) {
let browser = this.weakBrowser ? this.weakBrowser.get() : null;
if (browser) {
browser.messageManager.sendAsyncMessage(
"FormDateTime:PickerValueChanged", aEvent.detail);
}
},
// Get picker from browser and show it anchored to the input box.
async showPicker(aBrowser, aData) {
let rect = aData.rect;
let type = aData.type;
let detail = aData.detail;
- this._anchor = aBrowser.ownerGlobal.gBrowser.popupAnchor;
- this._anchor.left = rect.left;
- this._anchor.top = rect.top;
- this._anchor.width = rect.width;
- this._anchor.height = rect.height;
- this._anchor.hidden = false;
-
- debug("Opening picker with details: " + JSON.stringify(detail));
-
let window = aBrowser.ownerGlobal;
let tabbrowser = window.gBrowser;
if (Services.focus.activeWindow != window ||
tabbrowser.selectedBrowser != aBrowser) {
// We were sent a message from a window or tab that went into the
// background, so we'll ignore it for now.
+ this._sendCloseEvent(aBrowser);
+ this._closePicker();
return;
}
+ let winUtils = window.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIDOMWindowUtils);
+ let docRect = winUtils.getBoundsWithoutFlushing(tabbrowser.selectedBrowser);
+
+ let anchorRect = {};
+ anchorRect.left = docRect.left + rect.left;
+ anchorRect.top = docRect.top + rect.top;
+ anchorRect.height = rect.height;
+ anchorRect.width = rect.width;
+ anchorRect.bottom = anchorRect.top + rect.height;
+ anchorRect.right = anchorRect.left + rect.width;
+
+ /* if the anchor is outside of the document don't display */
+ if (!(anchorRect.left >= docRect.left &&
+ anchorRect.right <= docRect.right &&
+ anchorRect.top >= docRect.top &&
+ anchorRect.bottom <= docRect.bottom)) {
+ // As the page might have scrolled from the previously calculated placement, we should hide the popup if showing
+ this._sendCloseEvent(aBrowser);
+ this._closePicker();
+ return;
+ }
+
+ this._anchor = aBrowser.ownerGlobal.gBrowser.popupAnchor;
+ this._anchor.left = anchorRect.left;
+ this._anchor.top = anchorRect.top;
+ this._anchor.width = anchorRect.width;
+ this._anchor.height = anchorRect.height;
+ this._anchor.hidden = false;
+
+ debug("Opening picker with details: " + JSON.stringify(detail));
+
this.weakBrowser = Cu.getWeakReference(aBrowser);
this.picker = aBrowser.dateTimePicker;
if (!this.picker) {
debug("aBrowser.dateTimePicker not found, exiting now.");
return;
}
// The datetimepopup binding is only attached when it is needed.
// Check if openPicker method is present to determine if binding has