Bug 874050, Make Nofification's click event cancelable, r=wchen

--HG--
extra : rebase_source : c78a2bb9943c67e9baa7cf9a2af6bbe8188f0040
This commit is contained in:
Olli Pettay 2014-10-14 22:53:58 +03:00
Родитель 6fea005a92
Коммит f2e3fa5a63
6 изменённых файлов: 199 добавлений и 91 удалений

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

@ -1,58 +1,92 @@
/* Any copyright is dedicated to the Public Domain. /* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/ * http://creativecommons.org/publicdomain/zero/1.0/
*/ */
"use strict"; "use strict";
let tab; let tab;
let notification; let notification;
let notificationURL = "http://example.org/browser/browser/base/content/test/general/file_dom_notifications.html"; let notificationURL = "http://example.org/browser/browser/base/content/test/general/file_dom_notifications.html";
function test () { function test () {
waitForExplicitFinish(); waitForExplicitFinish();
let pm = Services.perms; let pm = Services.perms;
registerCleanupFunction(function() { registerCleanupFunction(function() {
pm.remove(notificationURL, "desktop-notification"); pm.remove(notificationURL, "desktop-notification");
gBrowser.removeTab(tab); gBrowser.removeTab(tab);
window.restore(); window.restore();
}); });
pm.add(makeURI(notificationURL), "desktop-notification", pm.ALLOW_ACTION); pm.add(makeURI(notificationURL), "desktop-notification", pm.ALLOW_ACTION);
tab = gBrowser.addTab(notificationURL); tab = gBrowser.addTab(notificationURL);
tab.linkedBrowser.addEventListener("load", onLoad, true); tab.linkedBrowser.addEventListener("load", onLoad, true);
} }
function onLoad() { function onLoad() {
isnot(gBrowser.selectedTab, tab, "Notification page loaded as a background tab"); isnot(gBrowser.selectedTab, tab, "Notification page loaded as a background tab");
tab.linkedBrowser.removeEventListener("load", onLoad, true); tab.linkedBrowser.removeEventListener("load", onLoad, true);
let win = tab.linkedBrowser.contentWindow.wrappedJSObject; let win = tab.linkedBrowser.contentWindow.wrappedJSObject;
notification = win.showNotification(); win.newWindow = win.open("about:blank", "", "height=100,width=100");
notification.addEventListener("show", onAlertShowing); win.newWindow.addEventListener("load", function() {
} info("new window loaded");
win.newWindow.addEventListener("blur", function b() {
function onAlertShowing() { info("new window got blur");
info("Notification alert showing"); win.newWindow.removeEventListener("blur", b);
notification.removeEventListener("show", onAlertShowing); notification = win.showNotification1();
win.newWindow.addEventListener("focus", onNewWindowFocused);
let alertWindow = findChromeWindowByURI("chrome://global/content/alerts/alert.xul"); notification.addEventListener("show", onAlertShowing);
if (!alertWindow) { });
todo(false, "Notifications don't use XUL windows on all platforms.");
notification.close(); function waitUntilNewWindowHasFocus() {
finish(); if (!win.newWindow.document.hasFocus()) {
return; setTimeout(waitUntilNewWindowHasFocus, 50);
} } else {
gBrowser.tabContainer.addEventListener("TabSelect", onTabSelect); // Focus another window so that new window gets blur event.
EventUtils.synthesizeMouseAtCenter(alertWindow.document.getElementById("alertTitleLabel"), {}, alertWindow); gBrowser.selectedTab.linkedBrowser.contentWindow.focus();
info("Clicked on notification"); }
alertWindow.close(); }
} win.newWindow.focus();
waitUntilNewWindowHasFocus();
function onTabSelect() { });
gBrowser.tabContainer.removeEventListener("TabSelect", onTabSelect); }
is(gBrowser.selectedTab.linkedBrowser.contentWindow.location.href, notificationURL,
"Notification tab should be selected."); function onAlertShowing() {
info("Notification alert showing");
finish(); 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();
}

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

@ -1,23 +1,40 @@
<html> <html>
<head> <head>
<script> <script>
"use strict"; "use strict";
function showNotification() { function showNotification1() {
var options = { var options = {
dir: undefined, dir: undefined,
lang: undefined, lang: undefined,
body: "Test body", body: "Test body",
tag: "Test tag", tag: "Test tag",
icon: undefined, icon: undefined,
}; };
return new Notification("Test title", options); var n = new Notification("Test title", options);
} n.addEventListener("click", function(event) {
</script> event.preventDefault();
</head> dump("Should focus new window.");
<body> newWindow.focus();
<form id="notificationForm" onsubmit="showNotification();"> });
<input type="submit" value="Show notification" id="submit"/> return n;
</form> }
</body>
</html> 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>

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

@ -76,6 +76,7 @@ Event::ConstructorInit(EventTarget* aOwner,
} }
mPrivateDataDuplicated = false; mPrivateDataDuplicated = false;
mWantsPopupControlCheck = false;
if (aEvent) { if (aEvent) {
mEvent = aEvent; mEvent = aEvent;
@ -655,12 +656,20 @@ PopupAllowedForEvent(const char *eventName)
// static // static
PopupControlState PopupControlState
Event::GetEventPopupControlState(WidgetEvent* aEvent) Event::GetEventPopupControlState(WidgetEvent* aEvent, nsIDOMEvent* aDOMEvent)
{ {
// generally if an event handler is running, new windows are disallowed. // generally if an event handler is running, new windows are disallowed.
// check for exceptions: // check for exceptions:
PopupControlState abuse = openAbused; PopupControlState abuse = openAbused;
if (aDOMEvent && aDOMEvent->InternalDOMEvent()->GetWantsPopupControlCheck()) {
nsAutoString type;
aDOMEvent->GetType(type);
if (PopupAllowedForEvent(NS_ConvertUTF16toUTF8(type).get())) {
return openAllowed;
}
}
switch(aEvent->mClass) { switch(aEvent->mClass) {
case eBasicEventClass: case eBasicEventClass:
// For these following events only allow popups if they're // For these following events only allow popups if they're

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

@ -30,6 +30,7 @@ namespace dom {
class EventTarget; class EventTarget;
class ErrorEvent; class ErrorEvent;
class ProgressEvent; class ProgressEvent;
class WantsPopupControlCheck;
// Dummy class so we can cast through it to get from nsISupports to // Dummy class so we can cast through it to get from nsISupports to
// Event subclasses with only two non-ambiguous static casts. // Event subclasses with only two non-ambiguous static casts.
@ -113,7 +114,8 @@ public:
// Returns true if the event should be trusted. // Returns true if the event should be trusted.
bool Init(EventTarget* aGlobal); bool Init(EventTarget* aGlobal);
static PopupControlState GetEventPopupControlState(WidgetEvent* aEvent); static PopupControlState GetEventPopupControlState(WidgetEvent* aEvent,
nsIDOMEvent* aDOMEvent = nullptr);
static void PopupAllowedEventsChanged(); static void PopupAllowedEventsChanged();
@ -235,6 +237,17 @@ protected:
void SetEventType(const nsAString& aEventTypeArg); void SetEventType(const nsAString& aEventTypeArg);
already_AddRefed<nsIContent> GetTargetFromFrame(); 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 * IsChrome() returns true if aCx is chrome context or the event is created
* in chrome's thread. Otherwise, false. * in chrome's thread. Otherwise, false.
@ -248,6 +261,28 @@ protected:
bool mEventIsInternal; bool mEventIsInternal;
bool mPrivateDataDuplicated; bool mPrivateDataDuplicated;
bool mIsMainThreadEvent; 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 dom

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

@ -976,7 +976,7 @@ EventListenerManager::HandleEventInternal(nsPresContext* aPresContext,
nsAutoTObserverArray<Listener, 2>::EndLimitedIterator iter(mListeners); nsAutoTObserverArray<Listener, 2>::EndLimitedIterator iter(mListeners);
Maybe<nsAutoPopupStatePusher> popupStatePusher; Maybe<nsAutoPopupStatePusher> popupStatePusher;
if (mIsMainThreadELM) { if (mIsMainThreadELM) {
popupStatePusher.emplace(Event::GetEventPopupControlState(aEvent)); popupStatePusher.emplace(Event::GetEventPopupControlState(aEvent, *aDOMEvent));
} }
bool hasListener = false; bool hasListener = false;

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

@ -24,6 +24,7 @@
#include "nsIScriptSecurityManager.h" #include "nsIScriptSecurityManager.h"
#include "nsIXPConnect.h" #include "nsIXPConnect.h"
#include "mozilla/dom/PermissionMessageUtils.h" #include "mozilla/dom/PermissionMessageUtils.h"
#include "mozilla/dom/Event.h"
#include "mozilla/Services.h" #include "mozilla/Services.h"
#include "nsContentPermissionHelper.h" #include "nsContentPermissionHelper.h"
#ifdef MOZ_B2G #ifdef MOZ_B2G
@ -360,19 +361,31 @@ NotificationObserver::Observe(nsISupports* aSubject, const char* aTopic,
const char16_t* aData) const char16_t* aData)
{ {
nsCOMPtr<nsPIDOMWindow> window = mNotification->GetOwner(); nsCOMPtr<nsPIDOMWindow> window = mNotification->GetOwner();
if (!window) { if (!window || !window->IsCurrentInnerWindow()) {
// Window has been closed, this observer is not valid anymore // Window has been closed, this observer is not valid anymore
return NS_ERROR_FAILURE; return NS_ERROR_FAILURE;
} }
if (!strcmp("alertclickcallback", aTopic)) { if (!strcmp("alertclickcallback", aTopic)) {
nsIDocument* doc = window ? window->GetExtantDoc() : nullptr;
if (doc) { nsCOMPtr<nsIDOMEvent> event;
nsContentUtils::DispatchChromeEvent(doc, window, NS_NewDOMEvent(getter_AddRefs(event), mNotification, nullptr, nullptr);
NS_LITERAL_STRING("DOMWebNotificationClicked"), nsresult rv = event->InitEvent(NS_LITERAL_STRING("click"), false, true);
true, 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)) { } else if (!strcmp("alertfinished", aTopic)) {
nsCOMPtr<nsINotificationStorage> notificationStorage = nsCOMPtr<nsINotificationStorage> notificationStorage =
do_GetService(NS_NOTIFICATION_STORAGE_CONTRACTID); do_GetService(NS_NOTIFICATION_STORAGE_CONTRACTID);