Bug 1328304 - Hide notifications when the address bar has focus and the URL is being edited. r=johannh

MozReview-Commit-ID: 5GhCsA9Gi3f

--HG--
extra : rebase_source : 17befcf3c98465dc9bf6407a812e81d83bc185bc
This commit is contained in:
Paolo Amadini 2017-02-10 13:16:02 +00:00
Родитель e04a84bbc8
Коммит 4bf8de0d84
5 изменённых файлов: 204 добавлений и 78 удалений

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

@ -151,9 +151,16 @@ XPCOMUtils.defineLazyGetter(this, "PopupNotifications", function() {
let tmp = {};
Cu.import("resource://gre/modules/PopupNotifications.jsm", tmp);
try {
// Hide all notifications while the URL is being edited and the address bar
// has focus, including the virtual focus in the results popup.
let shouldSuppress = () => {
return gURLBar.getAttribute("pageproxystate") != "valid" &&
gURLBar.focused;
};
return new tmp.PopupNotifications(gBrowser,
document.getElementById("notification-popup"),
document.getElementById("notification-popup-box"));
document.getElementById("notification-popup-box"),
{ shouldSuppress });
} catch (ex) {
Cu.reportError(ex);
return null;
@ -2442,17 +2449,8 @@ function BrowserPageInfo(documentURL, initialTab, imageElement, frameOuterWindow
*
* @param aURI [optional]
* nsIURI to set. If this is unspecified, the current URI will be used.
* @param aOptions [optional]
* An object with the following properties:
* {
* isForLocationChange:
* Set to true to indicate that the function was invoked to respond
* to a location change event, rather than to reset the current URI
* value. This is useful to avoid calling PopupNotifications.jsm
* multiple times.
* }
*/
function URLBarSetURI(aURI, aOptions = {}) {
function URLBarSetURI(aURI) {
var value = gBrowser.userTypedValue;
var valid = false;
@ -2485,7 +2483,7 @@ function URLBarSetURI(aURI, aOptions = {}) {
gURLBar.value = value;
gURLBar.valueIsTyped = !valid;
SetPageProxyState(valid ? "valid" : "invalid", aOptions);
SetPageProxyState(valid ? "valid" : "invalid");
}
function losslessDecodeURI(aURI) {
@ -2592,19 +2590,15 @@ function UpdatePageProxyState() {
* related user interface elments should be shown because the URI in the
* location bar matches the loaded page. The string "invalid" indicates
* that the URI in the location bar is different than the loaded page.
* @param aOptions [optional]
* An object with the following properties:
* {
* isForLocationChange:
* Set to true to indicate that the function was invoked to respond
* to a location change event. This is useful to avoid calling
* PopupNotifications.jsm multiple times.
* }
*/
function SetPageProxyState(aState, aOptions = {}) {
function SetPageProxyState(aState) {
if (!gURLBar)
return;
let oldPageProxyState = gURLBar.getAttribute("pageproxystate");
// The "browser_urlbar_stop_pending.js" test uses a MutationObserver to do
// some verifications at this point, and it breaks if we don't write the
// attribute, even if it hasn't changed (bug 1338115).
gURLBar.setAttribute("pageproxystate", aState);
// the page proxy state is set to valid via OnLocationChange, which
@ -2616,14 +2610,26 @@ function SetPageProxyState(aState, aOptions = {}) {
gURLBar.removeEventListener("input", UpdatePageProxyState);
}
// Only need to call anchorVisibilityChange if the PopupNotifications object
// for this window has already been initialized (i.e. its getter no
// longer exists). If this is the result of a locations change, then we will
// already invoke PopupNotifications.locationChange separately.
if (!Object.getOwnPropertyDescriptor(window, "PopupNotifications").get &&
!aOptions.isForLocationChange) {
PopupNotifications.anchorVisibilityChange();
// After we've ensured that we've applied the listeners and updated the value
// of gLastValidURLStr, return early if the actual state hasn't changed.
if (oldPageProxyState == aState) {
return;
}
UpdatePopupNotificationsVisibility();
}
function UpdatePopupNotificationsVisibility() {
// Only need to do something if the PopupNotifications object for this window
// has already been initialized (i.e. its getter no longer exists).
if (Object.getOwnPropertyDescriptor(window, "PopupNotifications").get) {
return;
}
// Notify PopupNotifications that the visible anchors may have changed. This
// also checks the suppression state according to the "shouldSuppress"
// function defined earlier in this file.
PopupNotifications.anchorVisibilityChange();
}
function PageProxyClickHandler(aEvent) {
@ -4542,7 +4548,7 @@ var XULBrowserWindow = {
this.reloadCommand.removeAttribute("disabled");
}
URLBarSetURI(aLocationURI, { isForLocationChange: true });
URLBarSetURI(aLocationURI);
BookmarkingUI.onLocationChange();

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

@ -102,49 +102,126 @@ var tests = [
gBrowser.selectedTab = this.oldSelectedTab;
}
},
// Test that popupnotifications are anchored to the identity icon while
// editing the URL in the location bar, and restored to their anchors when the
// URL is reverted.
// Test that popupnotifications are hidden while editing the URL in the
// location bar, anchored to the identity icon when the focus is moved away
// from the location bar, and restored when the URL is reverted.
{ id: "Test#4",
*run() {
this.oldSelectedTab = gBrowser.selectedTab;
yield BrowserTestUtils.openNewForegroundTab(gBrowser, "http://example.com/");
for (let persistent of [false, true]) {
let shown = waitForNotificationPanel();
this.notifyObj = new BasicNotification(this.id);
this.notifyObj.anchorID = "geo-notification-icon";
this.notifyObj.addOptions({ persistent });
this.notification = showNotification(this.notifyObj);
yield shown;
let shownInitially = waitForNotificationPanel();
checkPopup(PopupNotifications.panel, this.notifyObj);
// Typing in the location bar should hide the notification.
let hidden = waitForNotificationPanelHidden();
gURLBar.select();
EventUtils.synthesizeKey("*", {});
yield hidden;
is(document.getElementById("geo-notification-icon").boxObject.width, 0,
"geo anchor shouldn't be visible");
// Moving focus to the next control should show the notifications again,
// anchored to the identity icon. We clear the URL bar before moving the
// focus so that the awesomebar popup doesn't get in the way.
shown = waitForNotificationPanel();
EventUtils.synthesizeKey("VK_BACK_SPACE", {});
EventUtils.synthesizeKey("VK_TAB", {});
yield shown;
is(PopupNotifications.panel.anchorNode.id, "identity-icon",
"notification anchored to identity icon");
// Moving focus to the location bar should hide the notification again.
hidden = waitForNotificationPanelHidden();
EventUtils.synthesizeKey("VK_TAB", { shiftKey: true });
yield hidden;
// Reverting the URL should show the notification again.
shown = waitForNotificationPanel();
EventUtils.synthesizeKey("VK_ESCAPE", {});
yield shown;
checkPopup(PopupNotifications.panel, this.notifyObj);
hidden = waitForNotificationPanelHidden();
this.notification.remove();
yield hidden;
}
goNext();
}
},
// Test that popupnotifications triggered while editing the URL in the
// location bar are only shown later when the URL is reverted.
{ id: "Test#5",
*run() {
for (let persistent of [false, true]) {
// Start editing the URL, ensuring that the awesomebar popup is hidden.
gURLBar.select();
EventUtils.synthesizeKey("*", {});
EventUtils.synthesizeKey("VK_BACK_SPACE", {});
// Trying to show a notification should display nothing.
let notShowing = promiseTopicObserved("PopupNotifications-updateNotShowing");
this.notifyObj = new BasicNotification(this.id);
this.notifyObj.anchorID = "geo-notification-icon";
this.notifyObj.addOptions({ persistent });
this.notification = showNotification(this.notifyObj);
yield notShowing;
// Reverting the URL should show the notification.
let shown = waitForNotificationPanel();
EventUtils.synthesizeKey("VK_ESCAPE", {});
yield shown;
checkPopup(PopupNotifications.panel, this.notifyObj);
let hidden = waitForNotificationPanelHidden();
this.notification.remove();
yield hidden;
}
goNext();
}
},
// Test that persistent panels are still open after switching to another tab
// and back, even while editing the URL in the new tab.
{ id: "Test#6",
*run() {
let shown = waitForNotificationPanel();
this.notifyObj = new BasicNotification(this.id);
this.notifyObj.anchorID = "geo-notification-icon";
this.notifyObj.addOptions({
persistent: true,
});
this.notification = showNotification(this.notifyObj);
yield shownInitially;
yield shown;
checkPopup(PopupNotifications.panel, this.notifyObj);
// Switching to a new tab should hide the notification.
let hidden = waitForNotificationPanelHidden();
this.oldSelectedTab = gBrowser.selectedTab;
yield BrowserTestUtils.openNewForegroundTab(gBrowser, "http://example.com/");
yield hidden;
let shownAgain = waitForNotificationPanel();
// This will cause the popup to hide and show again.
// Start editing the URL.
gURLBar.select();
EventUtils.synthesizeKey("*", {});
// Keep the URL bar empty, so we don't show the awesomebar.
EventUtils.synthesizeKey("VK_BACK_SPACE", {});
yield shownAgain;
is(document.getElementById("geo-notification-icon").boxObject.width, 0,
"geo anchor shouldn't be visible");
is(PopupNotifications.panel.anchorNode.id, "identity-icon",
"notification anchored to identity icon");
let shownLastTime = waitForNotificationPanel();
// This will cause the popup to hide and show again.
EventUtils.synthesizeKey("VK_ESCAPE", {});
yield shownLastTime;
// Switching to the old tab should show the notification again.
shown = waitForNotificationPanel();
gBrowser.removeTab(gBrowser.selectedTab);
gBrowser.selectedTab = this.oldSelectedTab;
yield shown;
checkPopup(PopupNotifications.panel, this.notifyObj);
let hidden = new Promise(resolve => onPopupEvent("popuphidden", resolve));
hidden = waitForNotificationPanelHidden();
this.notification.remove();
gBrowser.removeTab(gBrowser.selectedTab);
gBrowser.selectedTab = this.oldSelectedTab;
yield hidden;
goNext();

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

@ -264,6 +264,14 @@ function waitForNotificationPanel() {
});
}
function waitForNotificationPanelHidden() {
return new Promise(resolve => {
onPopupEvent("popuphidden", function() {
resolve(this);
});
});
}
function triggerMainCommand(popup) {
let notifications = popup.childNodes;
ok(notifications.length > 0, "at least one notification displayed");

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

@ -1227,6 +1227,9 @@ file, You can obtain one at http://mozilla.org/MPL/2.0/.
if (event.originalTarget == this.inputField) {
this._hideURLTooltip();
this.formatValue();
if (this.getAttribute("pageproxystate") != "valid") {
UpdatePopupNotificationsVisibility();
}
}
]]></handler>
@ -1234,6 +1237,9 @@ file, You can obtain one at http://mozilla.org/MPL/2.0/.
if (event.originalTarget == this.inputField) {
this._clearNoActions();
this.formatValue();
if (this.getAttribute("pageproxystate") != "valid") {
UpdatePopupNotificationsVisibility();
}
}
if (ExtensionSearchHandler.hasActiveInputSession()) {
ExtensionSearchHandler.handleInputCancelled();

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

@ -202,8 +202,17 @@ Notification.prototype = {
* parent of anchor elements whose IDs are passed to show().
* It is used as a fallback popup anchor if notifications specify
* invalid or non-existent anchor IDs.
* @param options
* An optional object with the following optional properties:
* {
* shouldSuppress:
* If this function returns true, then all notifications are
* suppressed for this window. This state is checked on construction
* and when the "anchorVisibilityChange" method is called.
* }
*/
this.PopupNotifications = function PopupNotifications(tabbrowser, panel, iconBox) {
this.PopupNotifications = function PopupNotifications(tabbrowser, panel,
iconBox, options = {}) {
if (!(tabbrowser instanceof Ci.nsIDOMXULElement))
throw "Invalid tabbrowser";
if (iconBox && !(iconBox instanceof Ci.nsIDOMXULElement))
@ -211,6 +220,9 @@ this.PopupNotifications = function PopupNotifications(tabbrowser, panel, iconBox
if (!(panel instanceof Ci.nsIDOMXULElement))
throw "Invalid panel";
this._shouldSuppress = options.shouldSuppress || (() => false);
this._suppress = this._shouldSuppress();
this.window = tabbrowser.ownerGlobal;
this.panel = panel;
this.tabbrowser = tabbrowser;
@ -524,25 +536,35 @@ PopupNotifications.prototype = {
this._setNotificationsForBrowser(aBrowser, notifications);
if (this._isActiveBrowser(aBrowser)) {
// get the anchor element if the browser has defined one so it will
// _update will handle both the tabs iconBox and non-tab permission
// anchors.
this._update(notifications, this._getAnchorsForNotifications(notifications,
getAnchorFromBrowser(aBrowser)));
this.anchorVisibilityChange();
}
},
/**
* Called by the consumer to indicate that the visibility of the notification
* anchors may have changed, but the location has not changed. This may result
* in the "showing" and "shown" events for visible notifications to be
* invoked even if the anchor has not changed.
* anchors may have changed, but the location has not changed. This also
* checks whether all notifications are suppressed for this window.
*
* Calling this method may result in the "showing" and "shown" events for
* visible notifications to be invoked even if the anchor has not changed.
*/
anchorVisibilityChange() {
let notifications =
this._getNotificationsForBrowser(this.tabbrowser.selectedBrowser);
this._update(notifications, this._getAnchorsForNotifications(notifications,
getAnchorFromBrowser(this.tabbrowser.selectedBrowser)));
let suppress = this._shouldSuppress();
if (!suppress) {
// If notifications are not suppressed, always update the visibility.
this._suppress = false;
let notifications =
this._getNotificationsForBrowser(this.tabbrowser.selectedBrowser);
this._update(notifications, this._getAnchorsForNotifications(notifications,
getAnchorFromBrowser(this.tabbrowser.selectedBrowser)));
return;
}
// Notifications are suppressed, ensure that the panel is hidden.
if (!this._suppress) {
this._suppress = true;
this._hidePanel().catch(Cu.reportError);
}
},
/**
@ -1014,10 +1036,12 @@ PopupNotifications.prototype = {
}
// Filter out notifications that have been dismissed, unless they are
// persistent.
let notificationsToShow = notifications.filter(function(n) {
return (!n.dismissed || n.options.persistent) && !n.options.neverShow;
});
// persistent. Also check if we should not show any notification.
let notificationsToShow = [];
if (!this._suppress) {
notificationsToShow = notifications.filter(
n => (!n.dismissed || n.options.persistent) && !n.options.neverShow);
}
if (useIconBox) {
// Hide icons of the previous tab.
@ -1283,18 +1307,23 @@ PopupNotifications.prototype = {
},
_onPopupHidden: function PopupNotifications_onPopupHidden(event) {
if (event.target != this.panel || this._ignoreDismissal) {
if (this._ignoreDismissal) {
this._ignoreDismissal.resolve();
this._ignoreDismissal = null;
}
if (event.target != this.panel) {
return;
}
// Ensure that when the panel comes up without user interaction,
// we don't autofocus it.
// We may have removed the "noautofocus" attribute before showing the panel
// if it was opened with user interaction. When the panel is closed, we have
// to restore the attribute to its default value, so we don't autofocus it
// if it is subsequently opened from a different code path.
this.panel.setAttribute("noautofocus", "true");
// Handle the case where the panel was closed programmatically.
if (this._ignoreDismissal) {
this._ignoreDismissal.resolve();
this._ignoreDismissal = null;
return;
}
this._dismissOrRemoveCurrentNotifications();
this._clearPanel();