From f2e3fa5a63981a2e9a0286bf81318eddffbca866 Mon Sep 17 00:00:00 2001 From: Olli Pettay Date: Tue, 14 Oct 2014 22:53:58 +0300 Subject: [PATCH] Bug 874050, Make Nofification's click event cancelable, r=wchen --HG-- extra : rebase_source : c78a2bb9943c67e9baa7cf9a2af6bbe8188f0040 --- .../browser_notification_tab_switching.js | 150 +++++++++++------- .../test/general/file_dom_notifications.html | 63 +++++--- dom/events/Event.cpp | 11 +- dom/events/Event.h | 37 ++++- dom/events/EventListenerManager.cpp | 2 +- dom/notification/Notification.cpp | 27 +++- 6 files changed, 199 insertions(+), 91 deletions(-) diff --git a/browser/base/content/test/general/browser_notification_tab_switching.js b/browser/base/content/test/general/browser_notification_tab_switching.js index f1f568daad33..5f3c0802fec6 100644 --- a/browser/base/content/test/general/browser_notification_tab_switching.js +++ b/browser/base/content/test/general/browser_notification_tab_switching.js @@ -1,58 +1,92 @@ -/* Any copyright is dedicated to the Public Domain. - * http://creativecommons.org/publicdomain/zero/1.0/ - */ - -"use strict"; - -let tab; -let notification; -let notificationURL = "http://example.org/browser/browser/base/content/test/general/file_dom_notifications.html"; - -function test () { - waitForExplicitFinish(); - - let pm = Services.perms; - registerCleanupFunction(function() { - pm.remove(notificationURL, "desktop-notification"); - gBrowser.removeTab(tab); - window.restore(); - }); - - pm.add(makeURI(notificationURL), "desktop-notification", pm.ALLOW_ACTION); - - tab = gBrowser.addTab(notificationURL); - tab.linkedBrowser.addEventListener("load", onLoad, true); -} - -function onLoad() { - isnot(gBrowser.selectedTab, tab, "Notification page loaded as a background tab"); - tab.linkedBrowser.removeEventListener("load", onLoad, true); - let win = tab.linkedBrowser.contentWindow.wrappedJSObject; - notification = win.showNotification(); - notification.addEventListener("show", onAlertShowing); -} - -function onAlertShowing() { - info("Notification alert showing"); - notification.removeEventListener("show", onAlertShowing); - - let alertWindow = findChromeWindowByURI("chrome://global/content/alerts/alert.xul"); - if (!alertWindow) { - todo(false, "Notifications don't use XUL windows on all platforms."); - notification.close(); - finish(); - return; - } - gBrowser.tabContainer.addEventListener("TabSelect", onTabSelect); - EventUtils.synthesizeMouseAtCenter(alertWindow.document.getElementById("alertTitleLabel"), {}, alertWindow); - info("Clicked on notification"); - alertWindow.close(); -} - -function onTabSelect() { - gBrowser.tabContainer.removeEventListener("TabSelect", onTabSelect); - is(gBrowser.selectedTab.linkedBrowser.contentWindow.location.href, notificationURL, - "Notification tab should be selected."); - - finish(); -} +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +"use strict"; + +let tab; +let notification; +let notificationURL = "http://example.org/browser/browser/base/content/test/general/file_dom_notifications.html"; + +function test () { + waitForExplicitFinish(); + + let pm = Services.perms; + registerCleanupFunction(function() { + pm.remove(notificationURL, "desktop-notification"); + gBrowser.removeTab(tab); + window.restore(); + }); + + pm.add(makeURI(notificationURL), "desktop-notification", pm.ALLOW_ACTION); + + tab = gBrowser.addTab(notificationURL); + tab.linkedBrowser.addEventListener("load", onLoad, true); +} + +function onLoad() { + isnot(gBrowser.selectedTab, tab, "Notification page loaded as a background tab"); + tab.linkedBrowser.removeEventListener("load", onLoad, true); + let win = tab.linkedBrowser.contentWindow.wrappedJSObject; + win.newWindow = win.open("about:blank", "", "height=100,width=100"); + win.newWindow.addEventListener("load", function() { + info("new window loaded"); + win.newWindow.addEventListener("blur", function b() { + info("new window got blur"); + win.newWindow.removeEventListener("blur", b); + notification = win.showNotification1(); + win.newWindow.addEventListener("focus", onNewWindowFocused); + notification.addEventListener("show", onAlertShowing); + }); + + function waitUntilNewWindowHasFocus() { + if (!win.newWindow.document.hasFocus()) { + setTimeout(waitUntilNewWindowHasFocus, 50); + } else { + // Focus another window so that new window gets blur event. + gBrowser.selectedTab.linkedBrowser.contentWindow.focus(); + } + } + win.newWindow.focus(); + waitUntilNewWindowHasFocus(); + }); +} + +function onAlertShowing() { + info("Notification alert showing"); + notification.removeEventListener("show", onAlertShowing); + + let alertWindow = findChromeWindowByURI("chrome://global/content/alerts/alert.xul"); + if (!alertWindow) { + todo(false, "Notifications don't use XUL windows on all platforms."); + notification.close(); + finish(); + return; + } + gBrowser.tabContainer.addEventListener("TabSelect", onTabSelect); + EventUtils.synthesizeMouseAtCenter(alertWindow.document.getElementById("alertTitleLabel"), {}, alertWindow); + info("Clicked on notification"); + alertWindow.close(); +} + +function onNewWindowFocused(event) { + event.target.close(); + isnot(gBrowser.selectedTab, tab, "Notification page loaded as a background tab"); + // Using timeout to test that something do *not* happen! + setTimeout(openSecondNotification, 50); +} + +function openSecondNotification() { + isnot(gBrowser.selectedTab, tab, "Notification page loaded as a background tab"); + let win = tab.linkedBrowser.contentWindow.wrappedJSObject; + notification = win.showNotification2(); + notification.addEventListener("show", onAlertShowing); +} + +function onTabSelect() { + gBrowser.tabContainer.removeEventListener("TabSelect", onTabSelect); + is(gBrowser.selectedTab.linkedBrowser.contentWindow.location.href, notificationURL, + "Notification tab should be selected."); + + finish(); +} diff --git a/browser/base/content/test/general/file_dom_notifications.html b/browser/base/content/test/general/file_dom_notifications.html index 8791d5a1a5be..078c94a42d18 100644 --- a/browser/base/content/test/general/file_dom_notifications.html +++ b/browser/base/content/test/general/file_dom_notifications.html @@ -1,23 +1,40 @@ - - - - - -
- -
- - + + + + + +
+ +
+ + diff --git a/dom/events/Event.cpp b/dom/events/Event.cpp index cebeeffad75e..d2be9cf83985 100644 --- a/dom/events/Event.cpp +++ b/dom/events/Event.cpp @@ -76,6 +76,7 @@ Event::ConstructorInit(EventTarget* aOwner, } mPrivateDataDuplicated = false; + mWantsPopupControlCheck = false; if (aEvent) { mEvent = aEvent; @@ -655,12 +656,20 @@ PopupAllowedForEvent(const char *eventName) // static PopupControlState -Event::GetEventPopupControlState(WidgetEvent* aEvent) +Event::GetEventPopupControlState(WidgetEvent* aEvent, nsIDOMEvent* aDOMEvent) { // generally if an event handler is running, new windows are disallowed. // check for exceptions: PopupControlState abuse = openAbused; + if (aDOMEvent && aDOMEvent->InternalDOMEvent()->GetWantsPopupControlCheck()) { + nsAutoString type; + aDOMEvent->GetType(type); + if (PopupAllowedForEvent(NS_ConvertUTF16toUTF8(type).get())) { + return openAllowed; + } + } + switch(aEvent->mClass) { case eBasicEventClass: // For these following events only allow popups if they're diff --git a/dom/events/Event.h b/dom/events/Event.h index de2c1db7bc85..1ffd085fe84c 100644 --- a/dom/events/Event.h +++ b/dom/events/Event.h @@ -30,6 +30,7 @@ namespace dom { class EventTarget; class ErrorEvent; class ProgressEvent; +class WantsPopupControlCheck; // Dummy class so we can cast through it to get from nsISupports to // Event subclasses with only two non-ambiguous static casts. @@ -113,7 +114,8 @@ public: // Returns true if the event should be trusted. bool Init(EventTarget* aGlobal); - static PopupControlState GetEventPopupControlState(WidgetEvent* aEvent); + static PopupControlState GetEventPopupControlState(WidgetEvent* aEvent, + nsIDOMEvent* aDOMEvent = nullptr); static void PopupAllowedEventsChanged(); @@ -235,6 +237,17 @@ protected: void SetEventType(const nsAString& aEventTypeArg); already_AddRefed GetTargetFromFrame(); + friend class WantsPopupControlCheck; + void SetWantsPopupControlCheck(bool aCheck) + { + mWantsPopupControlCheck = aCheck; + } + + bool GetWantsPopupControlCheck() + { + return IsTrusted() && mWantsPopupControlCheck; + } + /** * IsChrome() returns true if aCx is chrome context or the event is created * in chrome's thread. Otherwise, false. @@ -248,6 +261,28 @@ protected: bool mEventIsInternal; bool mPrivateDataDuplicated; bool mIsMainThreadEvent; + // True when popup control check should rely on event.type, not + // WidgetEvent.message. + bool mWantsPopupControlCheck; +}; + +class MOZ_STACK_CLASS WantsPopupControlCheck +{ +public: + WantsPopupControlCheck(nsIDOMEvent* aEvent) : mEvent(aEvent->InternalDOMEvent()) + { + mOriginalWantsPopupControlCheck = mEvent->GetWantsPopupControlCheck(); + mEvent->SetWantsPopupControlCheck(mEvent->IsTrusted()); + } + + ~WantsPopupControlCheck() + { + mEvent->SetWantsPopupControlCheck(mOriginalWantsPopupControlCheck); + } + +private: + Event* mEvent; + bool mOriginalWantsPopupControlCheck; }; } // namespace dom diff --git a/dom/events/EventListenerManager.cpp b/dom/events/EventListenerManager.cpp index 288707af9c2c..82a55045d631 100644 --- a/dom/events/EventListenerManager.cpp +++ b/dom/events/EventListenerManager.cpp @@ -976,7 +976,7 @@ EventListenerManager::HandleEventInternal(nsPresContext* aPresContext, nsAutoTObserverArray::EndLimitedIterator iter(mListeners); Maybe popupStatePusher; if (mIsMainThreadELM) { - popupStatePusher.emplace(Event::GetEventPopupControlState(aEvent)); + popupStatePusher.emplace(Event::GetEventPopupControlState(aEvent, *aDOMEvent)); } bool hasListener = false; diff --git a/dom/notification/Notification.cpp b/dom/notification/Notification.cpp index 74e388cf9406..0d33cd14673c 100644 --- a/dom/notification/Notification.cpp +++ b/dom/notification/Notification.cpp @@ -24,6 +24,7 @@ #include "nsIScriptSecurityManager.h" #include "nsIXPConnect.h" #include "mozilla/dom/PermissionMessageUtils.h" +#include "mozilla/dom/Event.h" #include "mozilla/Services.h" #include "nsContentPermissionHelper.h" #ifdef MOZ_B2G @@ -360,19 +361,31 @@ NotificationObserver::Observe(nsISupports* aSubject, const char* aTopic, const char16_t* aData) { nsCOMPtr window = mNotification->GetOwner(); - if (!window) { + if (!window || !window->IsCurrentInnerWindow()) { // Window has been closed, this observer is not valid anymore return NS_ERROR_FAILURE; } if (!strcmp("alertclickcallback", aTopic)) { - nsIDocument* doc = window ? window->GetExtantDoc() : nullptr; - if (doc) { - nsContentUtils::DispatchChromeEvent(doc, window, - NS_LITERAL_STRING("DOMWebNotificationClicked"), - true, true); + + nsCOMPtr event; + NS_NewDOMEvent(getter_AddRefs(event), mNotification, nullptr, nullptr); + nsresult rv = event->InitEvent(NS_LITERAL_STRING("click"), false, true); + NS_ENSURE_SUCCESS(rv, rv); + event->SetTrusted(true); + WantsPopupControlCheck popupControlCheck(event); + bool doDefaultAction = true; + mNotification->DispatchEvent(event, &doDefaultAction); + if (doDefaultAction) { + nsIDocument* doc = window ? window->GetExtantDoc() : nullptr; + if (doc) { + // Browser UI may use DOMWebNotificationClicked to focus the tab + // from which the event was dispatched. + nsContentUtils::DispatchChromeEvent(doc, window->GetOuterWindow(), + NS_LITERAL_STRING("DOMWebNotificationClicked"), + true, true); + } } - mNotification->DispatchTrustedEvent(NS_LITERAL_STRING("click")); } else if (!strcmp("alertfinished", aTopic)) { nsCOMPtr notificationStorage = do_GetService(NS_NOTIFICATION_STORAGE_CONTRACTID);