Bug 874050, Make Nofification's click event cancelable, r=wchen
--- 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,95 @@
-/* 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";
+let newWindowOpenedFromTab;
+
+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");
+ newWindowOpenedFromTab = win.newWindow;
+ 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();
+ newWindowOpenedFromTab.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();
+}
--- a/browser/base/content/test/general/file_dom_notifications.html
+++ b/browser/base/content/test/general/file_dom_notifications.html
@@ -1,23 +1,40 @@
-<html>
-<head>
-<script>
-"use strict";
-
-function showNotification() {
- var options = {
- dir: undefined,
- lang: undefined,
- body: "Test body",
- tag: "Test tag",
- icon: undefined,
- };
- return new Notification("Test title", options);
-}
-</script>
-</head>
-<body>
-<form id="notificationForm" onsubmit="showNotification();">
- <input type="submit" value="Show notification" id="submit"/>
-</form>
-</body>
-</html>
+<html>
+<head>
+<script>
+"use strict";
+
+function showNotification1() {
+ var options = {
+ dir: undefined,
+ lang: undefined,
+ body: "Test body",
+ tag: "Test tag",
+ icon: undefined,
+ };
+ var n = new Notification("Test title", options);
+ n.addEventListener("click", function(event) {
+ event.preventDefault();
+ dump("Should focus new window.");
+ newWindow.focus();
+ });
+ return n;
+}
+
+function showNotification2() {
+ var options = {
+ dir: undefined,
+ lang: undefined,
+ body: "Test body",
+ tag: "Test tag",
+ icon: undefined,
+ };
+ return new Notification("Test title", options);
+}
+</script>
+</head>
+<body>
+<form id="notificationForm" onsubmit="showNotification();">
+ <input type="submit" value="Show notification" id="submit"/>
+</form>
+</body>
+</html>
--- a/dom/events/Event.cpp
+++ b/dom/events/Event.cpp
@@ -71,16 +71,17 @@ Event::ConstructorInit(EventTarget* aOwn
if (mIsMainThreadEvent && !sReturnHighResTimeStampIsSet) {
Preferences::AddBoolVarCache(&sReturnHighResTimeStamp,
"dom.event.highrestimestamp.enabled",
sReturnHighResTimeStamp);
sReturnHighResTimeStampIsSet = true;
}
mPrivateDataDuplicated = false;
+ mWantsPopupControlCheck = false;
if (aEvent) {
mEvent = aEvent;
mEventIsInternal = false;
}
else {
mEventIsInternal = true;
/*
@@ -650,22 +651,30 @@ PopupAllowedForEvent(const char *eventNa
startiter = enditer;
}
return false;
}
// 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
// triggered while handling user input. See
// nsPresShell::HandleEventInternal() for details.
if (EventStateManager::IsHandlingUserInput()) {
switch(aEvent->message) {
case NS_FORM_SELECTED :
--- a/dom/events/Event.h
+++ b/dom/events/Event.h
@@ -25,16 +25,17 @@ class nsIDOMEventTarget;
class nsPresContext;
namespace mozilla {
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.
class EventBase : public nsIDOMEvent
{
};
class Event : public EventBase,
@@ -108,17 +109,18 @@ public:
// nsIDOMEvent Interface
NS_DECL_NSIDOMEVENT
void InitPresContextData(nsPresContext* aPresContext);
// 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();
static void Shutdown();
static const char* GetEventName(uint32_t aEventType);
static CSSIntPoint GetClientCoords(nsPresContext* aPresContext,
WidgetEvent* aEvent,
@@ -230,29 +232,62 @@ public:
nsIContent* aRelatedTarget);
protected:
// Internal helper functions
void SetEventType(const nsAString& aEventTypeArg);
already_AddRefed<nsIContent> 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.
*/
bool IsChrome(JSContext* aCx) const;
mozilla::WidgetEvent* mEvent;
nsRefPtr<nsPresContext> mPresContext;
nsCOMPtr<EventTarget> mExplicitOriginalTarget;
nsCOMPtr<nsPIDOMWindow> mOwner; // nsPIDOMWindow for now.
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
} // namespace mozilla
#define NS_FORWARD_TO_EVENT \
NS_FORWARD_NSIDOMEVENT(Event::)
--- a/dom/events/EventListenerManager.cpp
+++ b/dom/events/EventListenerManager.cpp
@@ -971,17 +971,17 @@ EventListenerManager::HandleEventInterna
//Set the value of the internal PreventDefault flag properly based on aEventStatus
if (*aEventStatus == nsEventStatus_eConsumeNoDefault) {
aEvent->mFlags.mDefaultPrevented = true;
}
nsAutoTObserverArray<Listener, 2>::EndLimitedIterator iter(mListeners);
Maybe<nsAutoPopupStatePusher> popupStatePusher;
if (mIsMainThreadELM) {
- popupStatePusher.emplace(Event::GetEventPopupControlState(aEvent));
+ popupStatePusher.emplace(Event::GetEventPopupControlState(aEvent, *aDOMEvent));
}
bool hasListener = false;
while (iter.HasMore()) {
if (aEvent->mFlags.mImmediatePropagationStopped) {
break;
}
Listener* listener = &iter.GetNext();
--- a/dom/notification/Notification.cpp
+++ b/dom/notification/Notification.cpp
@@ -19,16 +19,17 @@
#include "nsServiceManagerUtils.h"
#include "nsStructuredCloneContainer.h"
#include "nsToolkitCompsCID.h"
#include "nsGlobalWindow.h"
#include "nsDOMJSUtils.h"
#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
#include "nsIDOMDesktopNotification.h"
#endif
namespace mozilla {
namespace dom {
@@ -355,29 +356,41 @@ NotificationTask::Run()
NS_IMPL_ISUPPORTS(NotificationObserver, nsIObserver)
NS_IMETHODIMP
NotificationObserver::Observe(nsISupports* aSubject, const char* aTopic,
const char16_t* aData)
{
nsCOMPtr<nsPIDOMWindow> 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<nsIDOMEvent> 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<nsINotificationStorage> notificationStorage =
do_GetService(NS_NOTIFICATION_STORAGE_CONTRACTID);
if (notificationStorage && mNotification->IsStored()) {
nsString origin;
nsresult rv = Notification::GetOrigin(mNotification->GetOwner(), origin);
if (NS_SUCCEEDED(rv)) {
nsString id;