зеркало из https://github.com/mozilla/gecko-dev.git
Bug 899574 - Part 2, add Notification.get() with notification storage. r=bent
--HG-- rename : dom/tests/mochitest/notification/create_notification.html => dom/tests/mochitest/notification/desktop-notification/create_notification.html rename : dom/tests/mochitest/notification/notification_common.js => dom/tests/mochitest/notification/desktop-notification/notification_common.js rename : dom/tests/mochitest/notification/test_basic_notification.html => dom/tests/mochitest/notification/desktop-notification/test_basic_notification.html rename : dom/tests/mochitest/notification/test_basic_notification_click.html => dom/tests/mochitest/notification/desktop-notification/test_basic_notification_click.html rename : dom/tests/mochitest/notification/test_leak_windowClose.html => dom/tests/mochitest/notification/desktop-notification/test_leak_windowClose.html rename : dom/tests/mochitest/notification/test_notification_tag.html => dom/tests/mochitest/notification/desktop-notification/test_notification_tag.html rename : dom/tests/mochitest/notification/test_system_principal.xul => dom/tests/mochitest/notification/desktop-notification/test_system_principal.xul
This commit is contained in:
Родитель
927a6de48d
Коммит
46d59dbe62
|
@ -11,6 +11,7 @@ Cu.import('resource://gre/modules/AlarmService.jsm');
|
|||
Cu.import('resource://gre/modules/ActivitiesService.jsm');
|
||||
Cu.import('resource://gre/modules/PermissionPromptHelper.jsm');
|
||||
Cu.import('resource://gre/modules/ObjectWrapper.jsm');
|
||||
Cu.import('resource://gre/modules/NotificationDB.jsm');
|
||||
Cu.import('resource://gre/modules/accessibility/AccessFu.jsm');
|
||||
Cu.import('resource://gre/modules/Payment.jsm');
|
||||
Cu.import("resource://gre/modules/AppsUtils.jsm");
|
||||
|
|
|
@ -348,6 +348,8 @@
|
|||
@BINPATH@/components/ContactManager.manifest
|
||||
@BINPATH@/components/PhoneNumberService.js
|
||||
@BINPATH@/components/PhoneNumberService.manifest
|
||||
@BINPATH@/components/NotificationStorage.js
|
||||
@BINPATH@/components/NotificationStorage.manifest
|
||||
@BINPATH@/components/PermissionSettings.js
|
||||
@BINPATH@/components/PermissionSettings.manifest
|
||||
@BINPATH@/components/PermissionPromptService.js
|
||||
|
|
|
@ -7,6 +7,7 @@ let Ci = Components.interfaces;
|
|||
let Cu = Components.utils;
|
||||
|
||||
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
Cu.import("resource://gre/modules/NotificationDB.jsm");
|
||||
Cu.import("resource:///modules/RecentWindow.jsm");
|
||||
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "Task",
|
||||
|
|
|
@ -528,6 +528,8 @@
|
|||
@BINPATH@/components/ContactManager.manifest
|
||||
@BINPATH@/components/PhoneNumberService.js
|
||||
@BINPATH@/components/PhoneNumberService.manifest
|
||||
@BINPATH@/components/NotificationStorage.js
|
||||
@BINPATH@/components/NotificationStorage.manifest
|
||||
@BINPATH@/components/AlarmsManager.js
|
||||
@BINPATH@/components/AlarmsManager.manifest
|
||||
@BINPATH@/components/Push.js
|
||||
|
|
|
@ -118,6 +118,10 @@ DOMInterfaces = {
|
|||
'resultNotAddRefed': [ 'playbackRate' ],
|
||||
},
|
||||
|
||||
'Notification' : {
|
||||
'nativeType': 'mozilla::dom::Notification'
|
||||
},
|
||||
|
||||
'AudioNode' : {
|
||||
'concrete': False,
|
||||
'binaryNames': {
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
|
||||
XPIDL_SOURCES += [
|
||||
'nsIDOMDesktopNotification.idl',
|
||||
'nsINotificationStorage.idl',
|
||||
]
|
||||
|
||||
XPIDL_MODULE = 'dom_notification'
|
||||
|
|
|
@ -0,0 +1,92 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#include "domstubs.idl"
|
||||
|
||||
[scriptable, uuid(fb089720-1c5c-11e3-b773-0800200c9a66)]
|
||||
interface nsINotificationStorageCallback : nsISupports
|
||||
{
|
||||
/**
|
||||
* Callback function used to pass single notification back
|
||||
* into C++ land for Notification.get return data.
|
||||
*
|
||||
* @param id: a uuid for this notification
|
||||
* @param title: the notification title
|
||||
* @param dir: the notification direction,
|
||||
* possible values are "ltr", "rtl", "auto"
|
||||
* @param lang: the notification language
|
||||
* @param body: the notification body
|
||||
* @param tag: the notification tag
|
||||
*/
|
||||
[implicit_jscontext]
|
||||
void handle(in DOMString id,
|
||||
in DOMString title,
|
||||
in DOMString dir,
|
||||
in DOMString lang,
|
||||
in DOMString body,
|
||||
in DOMString tag,
|
||||
in DOMString icon);
|
||||
|
||||
/**
|
||||
* Callback function used to notify C++ the we have returned
|
||||
* all notification objects for this Notification.get call.
|
||||
*/
|
||||
[implicit_jscontext]
|
||||
void done();
|
||||
};
|
||||
|
||||
/**
|
||||
* Interface for notification persistence layer.
|
||||
*/
|
||||
[scriptable, uuid(b177b080-2a23-11e3-8224-0800200c9a66)]
|
||||
interface nsINotificationStorage : nsISupports
|
||||
{
|
||||
|
||||
/**
|
||||
* Add/replace a notification to the persistence layer.
|
||||
*
|
||||
* @param origin: the origin/app of this notification
|
||||
* @param id: a uuid for this notification
|
||||
* @param title: the notification title
|
||||
* @param dir: the notification direction,
|
||||
* possible values are "ltr", "rtl", "auto"
|
||||
* @param lang: the notification language
|
||||
* @param body: the notification body
|
||||
* @param tag: notification tag, will replace any existing
|
||||
* notifications with same origin/tag pair
|
||||
*/
|
||||
void put(in DOMString origin,
|
||||
in DOMString id,
|
||||
in DOMString title,
|
||||
in DOMString dir,
|
||||
in DOMString lang,
|
||||
in DOMString body,
|
||||
in DOMString tag,
|
||||
in DOMString icon);
|
||||
|
||||
/**
|
||||
* Retrieve a list of notifications.
|
||||
*
|
||||
* @param origin: the origin/app for which to fetch notifications from
|
||||
* @param tag: used to fetch only a specific tag
|
||||
* @param callback: nsINotificationStorageCallback, used for
|
||||
* returning notifications objects
|
||||
*/
|
||||
void get(in DOMString origin,
|
||||
in DOMString tag,
|
||||
in nsINotificationStorageCallback aCallback);
|
||||
|
||||
/**
|
||||
* Remove a notification from storage.
|
||||
*
|
||||
* @param origin: the origin/app to delete the notification from
|
||||
* @param id: the uuid for the notification to delete
|
||||
*/
|
||||
void delete(in DOMString origin,
|
||||
in DOMString id);
|
||||
};
|
||||
|
||||
%{C++
|
||||
#define NS_NOTIFICATION_STORAGE_CONTRACTID "@mozilla.org/notificationStorage;1"
|
||||
%}
|
|
@ -5,6 +5,7 @@
|
|||
#include "PCOMContentPermissionRequestChild.h"
|
||||
#include "mozilla/dom/Notification.h"
|
||||
#include "mozilla/dom/OwningNonNull.h"
|
||||
#include "mozilla/dom/Promise.h"
|
||||
#include "mozilla/Preferences.h"
|
||||
#include "TabChild.h"
|
||||
#include "nsContentUtils.h"
|
||||
|
@ -12,20 +13,135 @@
|
|||
#include "nsIAlertsService.h"
|
||||
#include "nsIContentPermissionPrompt.h"
|
||||
#include "nsIDocument.h"
|
||||
#include "nsINotificationStorage.h"
|
||||
#include "nsIPermissionManager.h"
|
||||
#include "nsIUUIDGenerator.h"
|
||||
#include "nsServiceManagerUtils.h"
|
||||
#include "nsToolkitCompsCID.h"
|
||||
#include "nsGlobalWindow.h"
|
||||
#include "nsDOMJSUtils.h"
|
||||
#include "nsIScriptSecurityManager.h"
|
||||
#include "nsIAppsService.h"
|
||||
|
||||
#ifdef MOZ_B2G
|
||||
#include "nsIDOMDesktopNotification.h"
|
||||
#include "nsIAppsService.h"
|
||||
#endif
|
||||
|
||||
namespace mozilla {
|
||||
namespace dom {
|
||||
|
||||
class NotificationStorageCallback MOZ_FINAL : public nsINotificationStorageCallback
|
||||
{
|
||||
public:
|
||||
NS_DECL_CYCLE_COLLECTING_ISUPPORTS
|
||||
NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(NotificationStorageCallback)
|
||||
|
||||
NotificationStorageCallback(const GlobalObject& aGlobal, nsPIDOMWindow* aWindow, Promise* aPromise)
|
||||
: mCount(0),
|
||||
mGlobal(aGlobal.Get()),
|
||||
mWindow(aWindow),
|
||||
mPromise(aPromise)
|
||||
{
|
||||
MOZ_ASSERT(aWindow);
|
||||
MOZ_ASSERT(aPromise);
|
||||
JSContext* cx = aGlobal.GetContext();
|
||||
mNotifications = JS_NewArrayObject(cx, 0, nullptr);
|
||||
HoldData();
|
||||
}
|
||||
|
||||
NS_IMETHOD Handle(const nsAString& aID,
|
||||
const nsAString& aTitle,
|
||||
const nsAString& aDir,
|
||||
const nsAString& aLang,
|
||||
const nsAString& aBody,
|
||||
const nsAString& aTag,
|
||||
const nsAString& aIcon,
|
||||
JSContext* aCx)
|
||||
{
|
||||
MOZ_ASSERT(!aID.IsEmpty());
|
||||
MOZ_ASSERT(!aTitle.IsEmpty());
|
||||
|
||||
NotificationOptions options;
|
||||
options.mDir = Notification::StringToDirection(nsString(aDir));
|
||||
options.mLang = aLang;
|
||||
options.mBody = aBody;
|
||||
options.mTag = aTag;
|
||||
options.mIcon = aIcon;
|
||||
nsRefPtr<Notification> notification = Notification::CreateInternal(mWindow,
|
||||
aID,
|
||||
aTitle,
|
||||
options);
|
||||
JSAutoCompartment ac(aCx, mGlobal);
|
||||
JS::RootedObject scope(aCx, mGlobal);
|
||||
JS::RootedObject element(aCx, notification->WrapObject(aCx, scope));
|
||||
NS_ENSURE_TRUE(element, NS_ERROR_FAILURE);
|
||||
|
||||
if (!JS_DefineElement(aCx, mNotifications, mCount++,
|
||||
JS::ObjectValue(*element), nullptr, nullptr, 0)) {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHOD Done(JSContext* aCx)
|
||||
{
|
||||
JSAutoCompartment ac(aCx, mGlobal);
|
||||
Optional<JS::HandleValue> result(aCx, JS::ObjectValue(*mNotifications));
|
||||
mPromise->MaybeResolve(aCx, result);
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
private:
|
||||
~NotificationStorageCallback()
|
||||
{
|
||||
DropData();
|
||||
}
|
||||
|
||||
void HoldData()
|
||||
{
|
||||
mozilla::HoldJSObjects(this);
|
||||
}
|
||||
|
||||
void DropData()
|
||||
{
|
||||
mGlobal = nullptr;
|
||||
mNotifications = nullptr;
|
||||
mozilla::DropJSObjects(this);
|
||||
}
|
||||
|
||||
uint32_t mCount;
|
||||
JS::Heap<JSObject *> mGlobal;
|
||||
nsCOMPtr<nsPIDOMWindow> mWindow;
|
||||
nsRefPtr<Promise> mPromise;
|
||||
JS::Heap<JSObject *> mNotifications;
|
||||
};
|
||||
|
||||
NS_IMPL_CYCLE_COLLECTING_ADDREF(NotificationStorageCallback)
|
||||
NS_IMPL_CYCLE_COLLECTING_RELEASE(NotificationStorageCallback)
|
||||
NS_IMPL_CYCLE_COLLECTION_CLASS(NotificationStorageCallback)
|
||||
|
||||
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(NotificationStorageCallback)
|
||||
NS_INTERFACE_MAP_ENTRY(nsINotificationStorageCallback)
|
||||
NS_INTERFACE_MAP_ENTRY(nsISupports)
|
||||
NS_INTERFACE_MAP_END
|
||||
|
||||
NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(NotificationStorageCallback)
|
||||
NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mGlobal)
|
||||
NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mNotifications)
|
||||
NS_IMPL_CYCLE_COLLECTION_TRACE_END
|
||||
|
||||
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(NotificationStorageCallback)
|
||||
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_SCRIPT_OBJECTS
|
||||
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mWindow)
|
||||
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPromise)
|
||||
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
|
||||
|
||||
NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(NotificationStorageCallback)
|
||||
NS_IMPL_CYCLE_COLLECTION_UNLINK(mWindow)
|
||||
NS_IMPL_CYCLE_COLLECTION_UNLINK(mPromise)
|
||||
tmp->DropData();
|
||||
NS_IMPL_CYCLE_COLLECTION_UNLINK_END
|
||||
|
||||
class NotificationPermissionRequest : public nsIContentPermissionRequest,
|
||||
public PCOMContentPermissionRequestChild,
|
||||
public nsIRunnable
|
||||
|
@ -256,12 +372,15 @@ NotificationTask::Run()
|
|||
{
|
||||
switch (mAction) {
|
||||
case eShow:
|
||||
return mNotification->ShowInternal();
|
||||
mNotification->ShowInternal();
|
||||
break;
|
||||
case eClose:
|
||||
return mNotification->CloseInternal();
|
||||
mNotification->CloseInternal();
|
||||
break;
|
||||
default:
|
||||
MOZ_CRASH("Unexpected action for NotificationTask.");
|
||||
}
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMPL_ISUPPORTS1(NotificationObserver, nsIObserver)
|
||||
|
@ -282,50 +401,103 @@ NotificationObserver::Observe(nsISupports* aSubject, const char* aTopic,
|
|||
return NS_OK;
|
||||
}
|
||||
|
||||
Notification::Notification(const nsAString& aTitle, const nsAString& aBody,
|
||||
Notification::Notification(const nsAString& aID, const nsAString& aTitle, const nsAString& aBody,
|
||||
NotificationDirection aDir, const nsAString& aLang,
|
||||
const nsAString& aTag, const nsAString& aIconUrl)
|
||||
: mTitle(aTitle), mBody(aBody), mDir(aDir), mLang(aLang),
|
||||
: mID(aID), mTitle(aTitle), mBody(aBody), mDir(aDir), mLang(aLang),
|
||||
mTag(aTag), mIconUrl(aIconUrl), mIsClosed(false)
|
||||
{
|
||||
SetIsDOMBinding();
|
||||
}
|
||||
|
||||
// static
|
||||
already_AddRefed<Notification>
|
||||
Notification::Constructor(const GlobalObject& aGlobal,
|
||||
const nsAString& aTitle,
|
||||
const NotificationOptions& aOptions,
|
||||
ErrorResult& aRv)
|
||||
{
|
||||
nsString tag;
|
||||
if (aOptions.mTag.WasPassed()) {
|
||||
tag.Append(NS_LITERAL_STRING("tag:"));
|
||||
tag.Append(aOptions.mTag.Value());
|
||||
} else {
|
||||
tag.Append(NS_LITERAL_STRING("notag:"));
|
||||
tag.AppendInt(sCount++);
|
||||
}
|
||||
|
||||
nsRefPtr<Notification> notification = new Notification(aTitle,
|
||||
aOptions.mBody,
|
||||
aOptions.mDir,
|
||||
aOptions.mLang,
|
||||
tag,
|
||||
aOptions.mIcon);
|
||||
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
nsCOMPtr<nsPIDOMWindow> window = do_QueryInterface(aGlobal.GetAsSupports());
|
||||
MOZ_ASSERT(window, "Window should not be null.");
|
||||
notification->BindToOwner(window);
|
||||
nsRefPtr<Notification> notification = CreateInternal(window,
|
||||
EmptyString(),
|
||||
aTitle,
|
||||
aOptions);
|
||||
|
||||
// Queue a task to show the notification.
|
||||
nsCOMPtr<nsIRunnable> showNotificationTask =
|
||||
new NotificationTask(notification, NotificationTask::eShow);
|
||||
NS_DispatchToMainThread(showNotificationTask);
|
||||
NS_DispatchToCurrentThread(showNotificationTask);
|
||||
|
||||
// Persist the notification.
|
||||
nsresult rv;
|
||||
nsCOMPtr<nsINotificationStorage> notificationStorage =
|
||||
do_GetService(NS_NOTIFICATION_STORAGE_CONTRACTID, &rv);
|
||||
if (NS_FAILED(rv)) {
|
||||
aRv.Throw(rv);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
nsString origin;
|
||||
aRv = GetOrigin(window, origin);
|
||||
if (aRv.Failed()) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
nsString id;
|
||||
notification->GetID(id);
|
||||
aRv = notificationStorage->Put(origin,
|
||||
id,
|
||||
aTitle,
|
||||
DirectionToString(aOptions.mDir),
|
||||
aOptions.mLang,
|
||||
aOptions.mBody,
|
||||
aOptions.mTag,
|
||||
aOptions.mIcon);
|
||||
if (aRv.Failed()) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return notification.forget();
|
||||
}
|
||||
|
||||
nsresult
|
||||
already_AddRefed<Notification>
|
||||
Notification::CreateInternal(nsPIDOMWindow* aWindow,
|
||||
const nsAString& aID,
|
||||
const nsAString& aTitle,
|
||||
const NotificationOptions& aOptions)
|
||||
{
|
||||
nsString id;
|
||||
if (!aID.IsEmpty()) {
|
||||
id = aID;
|
||||
} else {
|
||||
nsCOMPtr<nsIUUIDGenerator> uuidgen =
|
||||
do_GetService("@mozilla.org/uuid-generator;1");
|
||||
NS_ENSURE_TRUE(uuidgen, nullptr);
|
||||
nsID uuid;
|
||||
nsresult rv = uuidgen->GenerateUUIDInPlace(&uuid);
|
||||
NS_ENSURE_SUCCESS(rv, nullptr);
|
||||
|
||||
char buffer[NSID_LENGTH];
|
||||
uuid.ToProvidedString(buffer);
|
||||
NS_ConvertASCIItoUTF16 convertedID(buffer);
|
||||
id = convertedID;
|
||||
}
|
||||
|
||||
nsRefPtr<Notification> notification = new Notification(id,
|
||||
aTitle,
|
||||
aOptions.mBody,
|
||||
aOptions.mDir,
|
||||
aOptions.mLang,
|
||||
aOptions.mTag,
|
||||
aOptions.mIcon);
|
||||
|
||||
notification->BindToOwner(aWindow);
|
||||
return notification.forget();
|
||||
}
|
||||
|
||||
void
|
||||
Notification::ShowInternal()
|
||||
{
|
||||
nsCOMPtr<nsIAlertsService> alertService =
|
||||
|
@ -336,7 +508,8 @@ Notification::ShowInternal()
|
|||
NotificationPermission::Granted || !alertService) {
|
||||
// We do not have permission to show a notification or alert service
|
||||
// is not available.
|
||||
return DispatchTrustedEvent(NS_LITERAL_STRING("error"));
|
||||
DispatchTrustedEvent(NS_LITERAL_STRING("error"));
|
||||
return;
|
||||
}
|
||||
|
||||
nsresult rv;
|
||||
|
@ -344,17 +517,18 @@ Notification::ShowInternal()
|
|||
if (mIconUrl.Length() > 0) {
|
||||
// Resolve image URL against document base URI.
|
||||
nsIDocument* doc = GetOwner()->GetExtantDoc();
|
||||
NS_ENSURE_TRUE(doc, NS_ERROR_UNEXPECTED);
|
||||
nsCOMPtr<nsIURI> baseUri = doc->GetBaseURI();
|
||||
NS_ENSURE_TRUE(baseUri, NS_ERROR_UNEXPECTED);
|
||||
nsCOMPtr<nsIURI> srcUri;
|
||||
rv = nsContentUtils::NewURIWithDocumentCharset(getter_AddRefs(srcUri),
|
||||
mIconUrl, doc, baseUri);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
if (srcUri) {
|
||||
nsAutoCString src;
|
||||
srcUri->GetSpec(src);
|
||||
absoluteUrl = NS_ConvertUTF8toUTF16(src);
|
||||
if (doc) {
|
||||
nsCOMPtr<nsIURI> baseUri = doc->GetBaseURI();
|
||||
if (baseUri) {
|
||||
nsCOMPtr<nsIURI> srcUri;
|
||||
rv = nsContentUtils::NewURIWithDocumentCharset(getter_AddRefs(srcUri),
|
||||
mIconUrl, doc, baseUri);
|
||||
if (NS_SUCCEEDED(rv)) {
|
||||
nsAutoCString src;
|
||||
srcUri->GetSpec(src);
|
||||
absoluteUrl = NS_ConvertUTF8toUTF16(src);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -362,7 +536,7 @@ Notification::ShowInternal()
|
|||
|
||||
nsString alertName;
|
||||
rv = GetAlertName(alertName);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
NS_ENSURE_SUCCESS_VOID(rv);
|
||||
|
||||
#ifdef MOZ_B2G
|
||||
nsCOMPtr<nsIAppNotificationService> appNotifier =
|
||||
|
@ -374,12 +548,15 @@ Notification::ShowInternal()
|
|||
if (appId != nsIScriptSecurityManager::UNKNOWN_APP_ID) {
|
||||
nsCOMPtr<nsIAppsService> appsService = do_GetService("@mozilla.org/AppsService;1");
|
||||
nsString manifestUrl = EmptyString();
|
||||
appsService->GetManifestURLByLocalId(appId, manifestUrl);
|
||||
return appNotifier->ShowAppNotification(mIconUrl, mTitle, mBody,
|
||||
true,
|
||||
manifestUrl,
|
||||
observer,
|
||||
alertName);
|
||||
rv = appsService->GetManifestURLByLocalId(appId, manifestUrl);
|
||||
if (NS_SUCCEEDED(rv)) {
|
||||
appNotifier->ShowAppNotification(mIconUrl, mTitle, mBody,
|
||||
true,
|
||||
manifestUrl,
|
||||
observer,
|
||||
alertName);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
@ -388,9 +565,9 @@ Notification::ShowInternal()
|
|||
// nsIObserver. Thus the cookie must be unique to differentiate observers.
|
||||
nsString uniqueCookie = NS_LITERAL_STRING("notification:");
|
||||
uniqueCookie.AppendInt(sCount++);
|
||||
return alertService->ShowAlertNotification(absoluteUrl, mTitle, mBody, true,
|
||||
uniqueCookie, observer, alertName,
|
||||
DirectionToString(mDir), mLang);
|
||||
alertService->ShowAlertNotification(absoluteUrl, mTitle, mBody, true,
|
||||
uniqueCookie, observer, alertName,
|
||||
DirectionToString(mDir), mLang);
|
||||
}
|
||||
|
||||
void
|
||||
|
@ -478,6 +655,47 @@ Notification::GetPermissionInternal(nsISupports* aGlobal, ErrorResult& aRv)
|
|||
}
|
||||
}
|
||||
|
||||
already_AddRefed<Promise>
|
||||
Notification::Get(const GlobalObject& aGlobal,
|
||||
const GetNotificationOptions& aFilter,
|
||||
ErrorResult& aRv)
|
||||
{
|
||||
nsCOMPtr<nsPIDOMWindow> window = do_QueryInterface(aGlobal.GetAsSupports());
|
||||
MOZ_ASSERT(window);
|
||||
nsIDocument* doc = window->GetExtantDoc();
|
||||
if (!doc) {
|
||||
aRv.Throw(NS_ERROR_UNEXPECTED);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
nsString origin;
|
||||
aRv = GetOrigin(window, origin);
|
||||
if (aRv.Failed()) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
nsresult rv;
|
||||
nsCOMPtr<nsINotificationStorage> notificationStorage =
|
||||
do_GetService(NS_NOTIFICATION_STORAGE_CONTRACTID, &rv);
|
||||
if (NS_FAILED(rv)) {
|
||||
aRv.Throw(rv);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
nsRefPtr<Promise> promise = new Promise(window);
|
||||
nsCOMPtr<nsINotificationStorageCallback> callback =
|
||||
new NotificationStorageCallback(aGlobal, window, promise);
|
||||
nsString tag = aFilter.mTag.WasPassed() ?
|
||||
aFilter.mTag.Value() :
|
||||
EmptyString();
|
||||
aRv = notificationStorage->Get(origin, tag, callback);
|
||||
if (aRv.Failed()) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return promise.forget();
|
||||
}
|
||||
|
||||
bool
|
||||
Notification::PrefEnabled()
|
||||
{
|
||||
|
@ -499,22 +717,61 @@ Notification::Close()
|
|||
NS_DispatchToMainThread(showNotificationTask);
|
||||
}
|
||||
|
||||
nsresult
|
||||
void
|
||||
Notification::CloseInternal()
|
||||
{
|
||||
if (!mIsClosed) {
|
||||
nsresult rv;
|
||||
// Don't bail out if notification storage fails, since we still
|
||||
// want to send the close event through the alert service.
|
||||
nsCOMPtr<nsINotificationStorage> notificationStorage =
|
||||
do_GetService(NS_NOTIFICATION_STORAGE_CONTRACTID);
|
||||
if (notificationStorage) {
|
||||
nsString origin;
|
||||
rv = GetOrigin(GetOwner(), origin);
|
||||
if (NS_SUCCEEDED(rv)) {
|
||||
notificationStorage->Delete(origin, mID);
|
||||
}
|
||||
}
|
||||
|
||||
nsCOMPtr<nsIAlertsService> alertService =
|
||||
do_GetService(NS_ALERTSERVICE_CONTRACTID);
|
||||
|
||||
if (alertService) {
|
||||
nsString alertName;
|
||||
nsresult rv = GetAlertName(alertName);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
rv = alertService->CloseAlert(alertName);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
rv = GetAlertName(alertName);
|
||||
if (NS_SUCCEEDED(rv)) {
|
||||
alertService->CloseAlert(alertName);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
nsresult
|
||||
Notification::GetOrigin(nsPIDOMWindow* aWindow, nsString& aOrigin)
|
||||
{
|
||||
MOZ_ASSERT(aWindow);
|
||||
nsresult rv;
|
||||
nsIDocument* doc = aWindow->GetExtantDoc();
|
||||
NS_ENSURE_TRUE(doc, NS_ERROR_UNEXPECTED);
|
||||
nsIPrincipal* principal = doc->NodePrincipal();
|
||||
NS_ENSURE_TRUE(principal, NS_ERROR_UNEXPECTED);
|
||||
|
||||
uint16_t appStatus = principal->GetAppStatus();
|
||||
uint32_t appId = principal->GetAppId();
|
||||
|
||||
if (appStatus == nsIPrincipal::APP_STATUS_NOT_INSTALLED ||
|
||||
appId == nsIScriptSecurityManager::NO_APP_ID ||
|
||||
appId == nsIScriptSecurityManager::UNKNOWN_APP_ID) {
|
||||
rv = nsContentUtils::GetUTFOrigin(principal, aOrigin);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
} else {
|
||||
// If we are in "app code", use manifest URL as unique origin since
|
||||
// multiple apps can share the same origin but not same notifications.
|
||||
nsCOMPtr<nsIAppsService> appsService =
|
||||
do_GetService("@mozilla.org/AppsService;1", &rv);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
appsService->GetManifestURLByLocalId(appId, aOrigin);
|
||||
}
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
@ -522,20 +779,12 @@ Notification::CloseInternal()
|
|||
nsresult
|
||||
Notification::GetAlertName(nsString& aAlertName)
|
||||
{
|
||||
// Get the notification name that is unique per origin + tag.
|
||||
// The name of the alert is of the form origin#tag
|
||||
|
||||
nsPIDOMWindow* owner = GetOwner();
|
||||
NS_ENSURE_TRUE(owner, NS_ERROR_UNEXPECTED);
|
||||
|
||||
nsIDocument* doc = owner->GetExtantDoc();
|
||||
NS_ENSURE_TRUE(doc, NS_ERROR_UNEXPECTED);
|
||||
|
||||
nsresult rv = nsContentUtils::GetUTFOrigin(doc->NodePrincipal(),
|
||||
aAlertName);
|
||||
// Get the notification name that is unique per origin + ID.
|
||||
// The name of the alert is of the form origin#ID.
|
||||
nsresult rv = GetOrigin(GetOwner(), aAlertName);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
aAlertName.AppendLiteral("#");
|
||||
aAlertName.Append(mTag);
|
||||
aAlertName.Append(mID);
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
|
|
|
@ -10,31 +10,37 @@
|
|||
#include "nsDOMEventTargetHelper.h"
|
||||
#include "nsIObserver.h"
|
||||
|
||||
#include "nsCycleCollectionParticipant.h"
|
||||
|
||||
namespace mozilla {
|
||||
namespace dom {
|
||||
|
||||
|
||||
class NotificationObserver;
|
||||
class Promise;
|
||||
|
||||
class Notification : public nsDOMEventTargetHelper
|
||||
{
|
||||
friend class NotificationTask;
|
||||
friend class NotificationPermissionRequest;
|
||||
friend class NotificationObserver;
|
||||
friend class NotificationStorageCallback;
|
||||
|
||||
public:
|
||||
IMPL_EVENT_HANDLER(click)
|
||||
IMPL_EVENT_HANDLER(show)
|
||||
IMPL_EVENT_HANDLER(error)
|
||||
IMPL_EVENT_HANDLER(close)
|
||||
|
||||
Notification(const nsAString& aTitle, const nsAString& aBody,
|
||||
NotificationDirection aDir, const nsAString& aLang,
|
||||
const nsAString& aTag, const nsAString& aIconUrl);
|
||||
|
||||
static already_AddRefed<Notification> Constructor(const GlobalObject& aGlobal,
|
||||
const nsAString& aTitle,
|
||||
const NotificationOptions& aOption,
|
||||
ErrorResult& aRv);
|
||||
void GetTitle(nsString& aRetval)
|
||||
void GetID(nsAString& aRetval) {
|
||||
aRetval = mID;
|
||||
}
|
||||
|
||||
void GetTitle(nsAString& aRetval)
|
||||
{
|
||||
aRetval = mTitle;
|
||||
}
|
||||
|
@ -44,24 +50,22 @@ public:
|
|||
return mDir;
|
||||
}
|
||||
|
||||
void GetLang(nsString& aRetval)
|
||||
void GetLang(nsAString& aRetval)
|
||||
{
|
||||
aRetval = mLang;
|
||||
}
|
||||
|
||||
void GetBody(nsString& aRetval)
|
||||
void GetBody(nsAString& aRetval)
|
||||
{
|
||||
aRetval = mBody;
|
||||
}
|
||||
|
||||
void GetTag(nsString& aRetval)
|
||||
void GetTag(nsAString& aRetval)
|
||||
{
|
||||
if (StringBeginsWith(mTag, NS_LITERAL_STRING("tag:"))) {
|
||||
aRetval = Substring(mTag, 4);
|
||||
}
|
||||
aRetval = mTag;
|
||||
}
|
||||
|
||||
void GetIcon(nsString& aRetval)
|
||||
void GetIcon(nsAString& aRetval)
|
||||
{
|
||||
aRetval = mIconUrl;
|
||||
}
|
||||
|
@ -73,6 +77,10 @@ public:
|
|||
static NotificationPermission GetPermission(const GlobalObject& aGlobal,
|
||||
ErrorResult& aRv);
|
||||
|
||||
static already_AddRefed<Promise> Get(const GlobalObject& aGlobal,
|
||||
const GetNotificationOptions& aFilter,
|
||||
ErrorResult& aRv);
|
||||
|
||||
void Close();
|
||||
|
||||
static bool PrefEnabled();
|
||||
|
@ -85,8 +93,17 @@ public:
|
|||
virtual JSObject* WrapObject(JSContext* aCx,
|
||||
JS::Handle<JSObject*> aScope) MOZ_OVERRIDE;
|
||||
protected:
|
||||
nsresult ShowInternal();
|
||||
nsresult CloseInternal();
|
||||
Notification(const nsAString& aID, const nsAString& aTitle, const nsAString& aBody,
|
||||
NotificationDirection aDir, const nsAString& aLang,
|
||||
const nsAString& aTag, const nsAString& aIconUrl);
|
||||
|
||||
static already_AddRefed<Notification> CreateInternal(nsPIDOMWindow* aWindow,
|
||||
const nsAString& aID,
|
||||
const nsAString& aTitle,
|
||||
const NotificationOptions& aOptions);
|
||||
|
||||
void ShowInternal();
|
||||
void CloseInternal();
|
||||
|
||||
static NotificationPermission GetPermissionInternal(nsISupports* aGlobal,
|
||||
ErrorResult& rv);
|
||||
|
@ -103,8 +120,22 @@ protected:
|
|||
}
|
||||
}
|
||||
|
||||
static const NotificationDirection StringToDirection(const nsAString& aDirection)
|
||||
{
|
||||
if (aDirection.EqualsLiteral("ltr")) {
|
||||
return NotificationDirection::Ltr;
|
||||
}
|
||||
if (aDirection.EqualsLiteral("rtl")) {
|
||||
return NotificationDirection::Rtl;
|
||||
}
|
||||
return NotificationDirection::Auto;
|
||||
}
|
||||
|
||||
static nsresult GetOrigin(nsPIDOMWindow* aWindow, nsString& aOrigin);
|
||||
|
||||
nsresult GetAlertName(nsString& aAlertName);
|
||||
|
||||
nsString mID;
|
||||
nsString mTitle;
|
||||
nsString mBody;
|
||||
NotificationDirection mDir;
|
||||
|
|
|
@ -0,0 +1,270 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
* You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
"use strict";
|
||||
|
||||
this.EXPORTED_SYMBOLS = [];
|
||||
|
||||
const DEBUG = false;
|
||||
function debug(s) { dump("-*- NotificationDB component: " + s + "\n"); }
|
||||
|
||||
const Cu = Components.utils;
|
||||
const Cc = Components.classes;
|
||||
const Ci = Components.interfaces;
|
||||
|
||||
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
Cu.import("resource://gre/modules/osfile.jsm");
|
||||
|
||||
XPCOMUtils.defineLazyServiceGetter(this, "ppmm",
|
||||
"@mozilla.org/parentprocessmessagemanager;1",
|
||||
"nsIMessageListenerManager");
|
||||
|
||||
XPCOMUtils.defineLazyGetter(this, "gEncoder", function() {
|
||||
return new TextEncoder();
|
||||
});
|
||||
|
||||
XPCOMUtils.defineLazyGetter(this, "gDecoder", function() {
|
||||
return new TextDecoder();
|
||||
});
|
||||
|
||||
|
||||
const NOTIFICATION_STORE_DIR = OS.Constants.Path.profileDir;
|
||||
const NOTIFICATION_STORE_PATH =
|
||||
OS.Path.join(NOTIFICATION_STORE_DIR, "notificationstore.json");
|
||||
|
||||
let NotificationDB = {
|
||||
init: function() {
|
||||
this.notifications = {};
|
||||
this.byTag = {};
|
||||
this.loaded = false;
|
||||
|
||||
this.tasks = []; // read/write operation queue
|
||||
this.runningTask = false;
|
||||
|
||||
ppmm.addMessageListener("Notification:Save", this);
|
||||
ppmm.addMessageListener("Notification:Delete", this);
|
||||
ppmm.addMessageListener("Notification:GetAll", this);
|
||||
},
|
||||
|
||||
// Attempt to read notification file, if it's not there we will create it.
|
||||
load: function(callback) {
|
||||
var promise = OS.File.read(NOTIFICATION_STORE_PATH);
|
||||
promise.then(
|
||||
function onSuccess(data) {
|
||||
try {
|
||||
this.notifications = JSON.parse(gDecoder.decode(data));
|
||||
} catch (e) {
|
||||
if (DEBUG) { debug("Unable to parse file data " + e); }
|
||||
}
|
||||
this.loaded = true;
|
||||
callback && callback();
|
||||
}.bind(this),
|
||||
|
||||
// If read failed, we assume we have no notifications to load.
|
||||
function onFailure(reason) {
|
||||
this.loaded = true;
|
||||
this.createStore(callback);
|
||||
}.bind(this)
|
||||
);
|
||||
},
|
||||
|
||||
// Creates the notification directory.
|
||||
createStore: function(callback) {
|
||||
var promise = OS.File.makeDir(NOTIFICATION_STORE_DIR, {
|
||||
ignoreExisting: true
|
||||
});
|
||||
promise.then(
|
||||
function onSuccess() {
|
||||
this.createFile(callback);
|
||||
}.bind(this),
|
||||
|
||||
function onFailure(reason) {
|
||||
if (DEBUG) { debug("Directory creation failed:" + reason); }
|
||||
callback && callback();
|
||||
}
|
||||
);
|
||||
},
|
||||
|
||||
// Creates the notification file once the directory is created.
|
||||
createFile: function(callback) {
|
||||
var promise = OS.File.open(NOTIFICATION_STORE_PATH, {create: true});
|
||||
promise.then(
|
||||
function onSuccess(handle) {
|
||||
callback && callback();
|
||||
},
|
||||
function onFailure(reason) {
|
||||
if (DEBUG) { debug("File creation failed:" + reason); }
|
||||
callback && callback();
|
||||
}
|
||||
);
|
||||
},
|
||||
|
||||
// Save current notifications to the file.
|
||||
save: function(callback) {
|
||||
var data = gEncoder.encode(JSON.stringify(this.notifications));
|
||||
var promise = OS.File.writeAtomic(NOTIFICATION_STORE_PATH, data);
|
||||
promise.then(
|
||||
function onSuccess() {
|
||||
callback && callback();
|
||||
},
|
||||
function onFailure(reason) {
|
||||
if (DEBUG) { debug("Save failed:" + reason); }
|
||||
callback && callback();
|
||||
}
|
||||
);
|
||||
},
|
||||
|
||||
// Helper function: callback will be called once file exists and/or is loaded.
|
||||
ensureLoaded: function(callback) {
|
||||
if (!this.loaded) {
|
||||
this.load(callback);
|
||||
} else {
|
||||
callback();
|
||||
}
|
||||
},
|
||||
|
||||
receiveMessage: function(message) {
|
||||
if (DEBUG) { debug("Received message:" + message.name); }
|
||||
|
||||
switch (message.name) {
|
||||
case "Notification:GetAll":
|
||||
this.queueTask("getall", message.data, function(notifications) {
|
||||
message.target.sendAsyncMessage("Notification:GetAll:Return:OK", {
|
||||
requestID: message.data.requestID,
|
||||
notifications: notifications
|
||||
});
|
||||
});
|
||||
break;
|
||||
|
||||
case "Notification:Save":
|
||||
this.queueTask("save", message.data, function() {
|
||||
message.target.sendAsyncMessage("Notification:Save:Return:OK", {
|
||||
requestID: message.data.requestID
|
||||
});
|
||||
});
|
||||
break;
|
||||
|
||||
case "Notification:Delete":
|
||||
this.queueTask("delete", message.data, function() {
|
||||
message.target.sendAsyncMessage("Notification:Delete:Return:OK", {
|
||||
requestID: message.data.requestID
|
||||
});
|
||||
});
|
||||
break;
|
||||
|
||||
default:
|
||||
if (DEBUG) { debug("Invalid message name" + message.name); }
|
||||
}
|
||||
},
|
||||
|
||||
// We need to make sure any read/write operations are atomic,
|
||||
// so use a queue to run each operation sequentially.
|
||||
queueTask: function(operation, data, callback) {
|
||||
if (DEBUG) { debug("Queueing task: " + operation); }
|
||||
this.tasks.push({
|
||||
operation: operation,
|
||||
data: data,
|
||||
callback: callback
|
||||
});
|
||||
|
||||
// Only run immediately if we aren't currently running another task.
|
||||
if (!this.runningTask) {
|
||||
if (DEBUG) { dump("Task queue was not running, starting now..."); }
|
||||
this.runNextTask();
|
||||
}
|
||||
},
|
||||
|
||||
runNextTask: function() {
|
||||
if (this.tasks.length === 0) {
|
||||
if (DEBUG) { dump("No more tasks to run, queue depleted"); }
|
||||
this.runningTask = false;
|
||||
return;
|
||||
}
|
||||
this.runningTask = true;
|
||||
|
||||
// Always make sure we are loaded before performing any read/write tasks.
|
||||
this.ensureLoaded(function() {
|
||||
var task = this.tasks.shift();
|
||||
|
||||
// Wrap the task callback to make sure we immediately
|
||||
// run the next task after running the original callback.
|
||||
var wrappedCallback = function() {
|
||||
if (DEBUG) { debug("Finishing task: " + task.operation); }
|
||||
task.callback.apply(this, arguments);
|
||||
this.runNextTask();
|
||||
}.bind(this);
|
||||
|
||||
switch (task.operation) {
|
||||
case "getall":
|
||||
this.taskGetAll(task.data, wrappedCallback);
|
||||
break;
|
||||
|
||||
case "save":
|
||||
this.taskSave(task.data, wrappedCallback);
|
||||
break;
|
||||
|
||||
case "delete":
|
||||
this.taskDelete(task.data, wrappedCallback);
|
||||
break;
|
||||
}
|
||||
}.bind(this));
|
||||
},
|
||||
|
||||
taskGetAll: function(data, callback) {
|
||||
if (DEBUG) { debug("Task, getting all"); }
|
||||
var origin = data.origin;
|
||||
var notifications = [];
|
||||
// Grab only the notifications for specified origin.
|
||||
for (var i in this.notifications[origin]) {
|
||||
notifications.push(this.notifications[origin][i]);
|
||||
}
|
||||
callback(notifications);
|
||||
},
|
||||
|
||||
taskSave: function(data, callback) {
|
||||
if (DEBUG) { debug("Task, saving"); }
|
||||
var origin = data.origin;
|
||||
var notification = data.notification;
|
||||
if (!this.notifications[origin]) {
|
||||
this.notifications[origin] = {};
|
||||
this.byTag[origin] = {};
|
||||
}
|
||||
|
||||
// We might have existing notification with this tag,
|
||||
// if so we need to remove it before saving the new one.
|
||||
if (notification.tag && this.byTag[origin][notification.tag]) {
|
||||
var oldNotification = this.byTag[origin][notification.tag];
|
||||
delete this.notifications[origin][oldNotification.id];
|
||||
this.byTag[origin][notification.tag] = notification;
|
||||
}
|
||||
|
||||
this.notifications[origin][notification.id] = notification;
|
||||
this.save(callback);
|
||||
},
|
||||
|
||||
taskDelete: function(data, callback) {
|
||||
if (DEBUG) { debug("Task, deleting"); }
|
||||
var origin = data.origin;
|
||||
var id = data.id;
|
||||
if (!this.notifications[origin]) {
|
||||
if (DEBUG) { debug("No notifications found for origin: " + origin); }
|
||||
return;
|
||||
}
|
||||
|
||||
// Make sure we can find the notification to delete.
|
||||
var oldNotification = this.notifications[origin][id];
|
||||
if (!oldNotification) {
|
||||
if (DEBUG) { debug("No notification found with id: " + id); }
|
||||
return;
|
||||
}
|
||||
|
||||
if (oldNotification.tag) {
|
||||
delete this.byTag[origin][oldNotification.tag];
|
||||
}
|
||||
delete this.notifications[origin][id];
|
||||
this.save(callback);
|
||||
}
|
||||
};
|
||||
|
||||
NotificationDB.init();
|
|
@ -0,0 +1,174 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
* You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
"use strict";
|
||||
|
||||
const DEBUG = false;
|
||||
function debug(s) { dump("-*- NotificationStorage.js: " + s + "\n"); }
|
||||
|
||||
const Cc = Components.classes;
|
||||
const Ci = Components.interfaces;
|
||||
const Cu = Components.utils;
|
||||
|
||||
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
|
||||
const NOTIFICATIONSTORAGE_CID = "{37f819b0-0b5c-11e3-8ffd-0800200c9a66}";
|
||||
const NOTIFICATIONSTORAGE_CONTRACTID = "@mozilla.org/notificationStorage;1";
|
||||
|
||||
XPCOMUtils.defineLazyServiceGetter(this, "cpmm",
|
||||
"@mozilla.org/childprocessmessagemanager;1",
|
||||
"nsIMessageSender");
|
||||
|
||||
|
||||
function NotificationStorage() {
|
||||
// cache objects
|
||||
this._notifications = {};
|
||||
this._byTag = {};
|
||||
this._cached = false;
|
||||
|
||||
this._requests = {};
|
||||
this._requestCount = 0;
|
||||
|
||||
// Register for message listeners.
|
||||
cpmm.addMessageListener("Notification:GetAll:Return:OK", this);
|
||||
}
|
||||
|
||||
NotificationStorage.prototype = {
|
||||
|
||||
put: function(origin, id, title, dir, lang, body, tag, icon) {
|
||||
if (DEBUG) { debug("PUT: " + id + ": " + title); }
|
||||
var notification = {
|
||||
id: id,
|
||||
title: title,
|
||||
dir: dir,
|
||||
lang: lang,
|
||||
body: body,
|
||||
tag: tag,
|
||||
icon: icon
|
||||
};
|
||||
|
||||
this._notifications[id] = notification;
|
||||
if (tag) {
|
||||
// We might have existing notification with this tag,
|
||||
// if so we need to remove it from our cache.
|
||||
if (this._byTag[tag]) {
|
||||
var oldNotification = this._byTag[tag];
|
||||
delete this._notifications[oldNotification.id];
|
||||
}
|
||||
|
||||
this._byTag[tag] = notification;
|
||||
};
|
||||
|
||||
cpmm.sendAsyncMessage("Notification:Save", {
|
||||
origin: origin,
|
||||
notification: notification
|
||||
});
|
||||
},
|
||||
|
||||
get: function(origin, tag, callback) {
|
||||
if (DEBUG) { debug("GET: " + tag); }
|
||||
if (this._cached) {
|
||||
this._fetchFromCache(tag, callback);
|
||||
} else {
|
||||
this._fetchFromDB(origin, tag, callback);
|
||||
}
|
||||
},
|
||||
|
||||
delete: function(origin, id) {
|
||||
if (DEBUG) { debug("DELETE: " + id); }
|
||||
var notification = this._notifications[id];
|
||||
if (notification) {
|
||||
if (notification.tag) {
|
||||
delete this._byTag[notification.tag];
|
||||
}
|
||||
delete this._notifications[id];
|
||||
}
|
||||
|
||||
cpmm.sendAsyncMessage("Notification:Delete", {
|
||||
origin: origin,
|
||||
id: id
|
||||
});
|
||||
},
|
||||
|
||||
receiveMessage: function(message) {
|
||||
switch (message.name) {
|
||||
case "Notification:GetAll:Return:OK":
|
||||
var request = this._requests[message.data.requestID];
|
||||
delete this._requests[message.data.requestID];
|
||||
this._populateCache(message.data.notifications);
|
||||
this._fetchFromCache(request.tag, request.callback);
|
||||
break;
|
||||
|
||||
default:
|
||||
if (DEBUG) debug("Unrecognized message: " + message.name);
|
||||
break;
|
||||
}
|
||||
},
|
||||
|
||||
_fetchFromDB: function(origin, tag, callback) {
|
||||
var request = {
|
||||
origin: origin,
|
||||
tag: tag,
|
||||
callback: callback
|
||||
};
|
||||
var requestID = this._requestCount++;
|
||||
this._requests[requestID] = request;
|
||||
cpmm.sendAsyncMessage("Notification:GetAll", {
|
||||
origin: origin,
|
||||
requestID: requestID
|
||||
});
|
||||
},
|
||||
|
||||
_fetchFromCache: function(tag, callback) {
|
||||
var notifications = [];
|
||||
// If a tag was specified and we have a notification
|
||||
// with this tag, return that. If no tag was specified
|
||||
// simple return all stored notifications.
|
||||
if (tag && this._byTag[tag]) {
|
||||
notifications.push(this._byTag[tag]);
|
||||
} else if (!tag) {
|
||||
for (var id in this._notifications) {
|
||||
notifications.push(this._notifications[id]);
|
||||
}
|
||||
}
|
||||
|
||||
// Pass each notification back separately.
|
||||
notifications.forEach(function(notification) {
|
||||
try {
|
||||
callback.handle(notification.id,
|
||||
notification.title,
|
||||
notification.dir,
|
||||
notification.lang,
|
||||
notification.body,
|
||||
notification.tag,
|
||||
notification.icon);
|
||||
} catch (e) {
|
||||
if (DEBUG) { debug("Error calling callback handle: " + e); }
|
||||
}
|
||||
});
|
||||
try {
|
||||
callback.done();
|
||||
} catch (e) {
|
||||
if (DEBUG) { debug("Error calling callback done: " + e); }
|
||||
}
|
||||
},
|
||||
|
||||
_populateCache: function(notifications) {
|
||||
notifications.forEach(function(notification) {
|
||||
this._notifications[notification.id] = notification;
|
||||
if (notification.tag) {
|
||||
this._byTag[notification.tag] = notification;
|
||||
}
|
||||
}.bind(this));
|
||||
this._cached = true;
|
||||
},
|
||||
|
||||
classID : Components.ID(NOTIFICATIONSTORAGE_CID),
|
||||
contractID : NOTIFICATIONSTORAGE_CONTRACTID,
|
||||
QueryInterface: XPCOMUtils.generateQI([Ci.nsINotificationStorage,
|
||||
Ci.nsIMessageListener]),
|
||||
};
|
||||
|
||||
|
||||
this.NSGetFactory = XPCOMUtils.generateNSGetFactory([NotificationStorage]);
|
|
@ -0,0 +1,3 @@
|
|||
# NotificationStorage.js
|
||||
component {37f819b0-0b5c-11e3-8ffd-0800200c9a66} NotificationStorage.js
|
||||
contract @mozilla.org/notificationStorage;1 {37f819b0-0b5c-11e3-8ffd-0800200c9a66}
|
|
@ -6,6 +6,15 @@
|
|||
|
||||
MODULE = 'dom'
|
||||
|
||||
EXTRA_COMPONENTS += [
|
||||
'NotificationStorage.js',
|
||||
'NotificationStorage.manifest',
|
||||
]
|
||||
|
||||
EXTRA_JS_MODULES += [
|
||||
'NotificationDB.jsm'
|
||||
]
|
||||
|
||||
EXPORTS.mozilla.dom += [
|
||||
'DesktopNotification.h',
|
||||
'Notification.h',
|
||||
|
|
|
@ -29,7 +29,3 @@ DIRS += [
|
|||
if CONFIG['MOZ_GAMEPAD']:
|
||||
DIRS += ['gamepad']
|
||||
|
||||
#needs IPC support, also tests do not run successfully in Firefox for now
|
||||
#if CONFIG['MOZ_BUILD_APP'] != 'mobile':
|
||||
# DIRS += ['notification']
|
||||
|
||||
|
|
|
@ -0,0 +1,81 @@
|
|||
var MockServices = (function () {
|
||||
"use strict";
|
||||
|
||||
const MOCK_ALERTS_CID = SpecialPowers.wrap(SpecialPowers.Components)
|
||||
.ID("{48068bc2-40ab-4904-8afd-4cdfb3a385f3}");
|
||||
const ALERTS_SERVICE_CONTRACT_ID = "@mozilla.org/alerts-service;1";
|
||||
|
||||
const MOCK_SYSTEM_ALERTS_CID = SpecialPowers.wrap(SpecialPowers.Components)
|
||||
.ID("{e86d888c-e41b-4b78-9104-2f2742a532de}");
|
||||
const SYSTEM_ALERTS_SERVICE_CONTRACT_ID = "@mozilla.org/system-alerts-service;1";
|
||||
|
||||
var registrar = SpecialPowers.wrap(SpecialPowers.Components).manager
|
||||
.QueryInterface(SpecialPowers.Ci.nsIComponentRegistrar);
|
||||
|
||||
var activeNotifications = Object.create(null);
|
||||
|
||||
var mockAlertsService = {
|
||||
showAlertNotification: function(imageUrl, title, text, textClickable,
|
||||
cookie, alertListener, name) {
|
||||
var listener = SpecialPowers.wrap(alertListener);
|
||||
activeNotifications[name] = {
|
||||
listener: listener,
|
||||
cookie: cookie
|
||||
};
|
||||
|
||||
// fake async alert show event
|
||||
setTimeout(function () {
|
||||
listener.observe(null, "alertshow", cookie);
|
||||
}, 100);
|
||||
|
||||
// ?? SpecialPowers.wrap(alertListener).observe(null, "alertclickcallback", cookie);
|
||||
},
|
||||
|
||||
showAppNotification: function(imageUrl, title, text, textClickable,
|
||||
manifestURL, alertListener, name) {
|
||||
this.showAlertNotification(imageUrl, title, text, textClickable, "", alertListener, name);
|
||||
},
|
||||
|
||||
closeAlert: function(name) {
|
||||
var notification = activeNotifications[name];
|
||||
if (notification) {
|
||||
notification.listener.observe(null, "alertfinished", notification.cookie);
|
||||
delete activeNotifications[name];
|
||||
}
|
||||
},
|
||||
|
||||
QueryInterface: function(aIID) {
|
||||
if (SpecialPowers.wrap(aIID).equals(SpecialPowers.Ci.nsISupports) ||
|
||||
SpecialPowers.wrap(aIID).equals(SpecialPowers.Ci.nsIAlertsService)) {
|
||||
return this;
|
||||
}
|
||||
throw SpecialPowers.Components.results.NS_ERROR_NO_INTERFACE;
|
||||
},
|
||||
|
||||
createInstance: function(aOuter, aIID) {
|
||||
if (aOuter != null) {
|
||||
throw SpecialPowers.Components.results.NS_ERROR_NO_AGGREGATION;
|
||||
}
|
||||
return this.QueryInterface(aIID);
|
||||
}
|
||||
};
|
||||
mockAlertsService = SpecialPowers.wrapCallbackObject(mockAlertsService);
|
||||
|
||||
// MockServices API
|
||||
return {
|
||||
register: function () {
|
||||
registrar.registerFactory(MOCK_ALERTS_CID, "alerts service",
|
||||
ALERTS_SERVICE_CONTRACT_ID,
|
||||
mockAlertsService);
|
||||
|
||||
registrar.registerFactory(MOCK_SYSTEM_ALERTS_CID, "system alerts service",
|
||||
SYSTEM_ALERTS_SERVICE_CONTRACT_ID,
|
||||
mockAlertsService);
|
||||
},
|
||||
|
||||
unregister: function () {
|
||||
registrar.unregisterFactory(MOCK_ALERTS_CID, mockAlertsService);
|
||||
registrar.unregisterFactory(MOCK_SYSTEM_ALERTS_CID, mockAlertsService);
|
||||
},
|
||||
};
|
||||
})();
|
|
@ -0,0 +1,73 @@
|
|||
var NotificationTest = (function () {
|
||||
"use strict";
|
||||
|
||||
function info(msg, name) {
|
||||
SimpleTest.info("::Notification Tests::" + (name || ""), msg);
|
||||
}
|
||||
|
||||
function setup_testing_env() {
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
// turn on testing pref (used by notification.cpp, and mock the alerts
|
||||
SpecialPowers.setBoolPref("notification.prompt.testing", true);
|
||||
}
|
||||
|
||||
function teardown_testing_env() {
|
||||
SimpleTest.finish();
|
||||
}
|
||||
|
||||
function executeTests(tests, callback) {
|
||||
// context is `this` object in test functions
|
||||
// it can be used to track data between tests
|
||||
var context = {};
|
||||
|
||||
(function executeRemainingTests(remainingTests) {
|
||||
if (!remainingTests.length) {
|
||||
return callback();
|
||||
}
|
||||
|
||||
var nextTest = remainingTests.shift();
|
||||
var finishTest = executeRemainingTests.bind(null, remainingTests);
|
||||
var startTest = nextTest.call.bind(nextTest, context, finishTest);
|
||||
|
||||
try {
|
||||
startTest();
|
||||
// if no callback was defined for test function,
|
||||
// we must manually invoke finish to continue
|
||||
if (nextTest.length === 0) {
|
||||
finishTest();
|
||||
}
|
||||
} catch (e) {
|
||||
ok(false, "Test threw exception!");
|
||||
finishTest();
|
||||
}
|
||||
})(tests);
|
||||
}
|
||||
|
||||
// NotificationTest API
|
||||
return {
|
||||
run: function (tests, callback) {
|
||||
setup_testing_env();
|
||||
|
||||
addLoadEvent(function () {
|
||||
executeTests(tests, function () {
|
||||
teardown_testing_env();
|
||||
callback && callback();
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
allowNotifications: function () {
|
||||
SpecialPowers.setBoolPref("notification.prompt.testing.allow", true);
|
||||
},
|
||||
|
||||
denyNotifications: function () {
|
||||
SpecialPowers.setBoolPref("notification.prompt.testing.allow", false);
|
||||
},
|
||||
|
||||
clickNotification: function (notification) {
|
||||
// TODO: how??
|
||||
},
|
||||
|
||||
info: info
|
||||
};
|
||||
})();
|
|
@ -0,0 +1,6 @@
|
|||
# -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*-
|
||||
# vim: set filetype=python:
|
||||
# This Source Code Form is subject to the terms of the Mozilla Public
|
||||
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
|
@ -1,10 +1,7 @@
|
|||
[DEFAULT]
|
||||
support-files =
|
||||
create_notification.html
|
||||
notification_common.js
|
||||
MockServices.js
|
||||
NotificationTest.js
|
||||
|
||||
[test_basic_notification.html]
|
||||
[test_basic_notification_click.html]
|
||||
[test_leak_windowClose.html]
|
||||
[test_notification_tag.html]
|
||||
[test_web_notifications.html]
|
||||
[test_notification_basics.html]
|
||||
[test_notification_storage.html]
|
||||
|
|
|
@ -0,0 +1,115 @@
|
|||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<head>
|
||||
<title>Notification Basics</title>
|
||||
<script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
|
||||
<script type="text/javascript" src="MockServices.js"></script>
|
||||
<script type="text/javascript" src="NotificationTest.js"></script>
|
||||
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
|
||||
</head>
|
||||
<body>
|
||||
<p id="display"></p>
|
||||
<div id="content" style="display: none"></div>
|
||||
<pre id="test"></pre>
|
||||
<script type="text/javascript">
|
||||
|
||||
var info = NotificationTest.info;
|
||||
|
||||
var steps = [
|
||||
function () {
|
||||
info("Test notification spec");
|
||||
ok(Notification, "Notification constructor exists");
|
||||
ok(Notification.permission, "Notification.permission exists");
|
||||
ok(Notification.requestPermission, "Notification.requestPermission exists");
|
||||
ok(Notification.get, "Notification.get exists");
|
||||
},
|
||||
|
||||
function () {
|
||||
info("Test blank requestPermission");
|
||||
Notification.requestPermission();
|
||||
},
|
||||
|
||||
function (done) {
|
||||
info("Test requestPermission deny");
|
||||
NotificationTest.denyNotifications();
|
||||
Notification.requestPermission(function(perm) {
|
||||
is(perm, "denied", "Permission should be denied.");
|
||||
is(Notification.permission, "denied", "Permission should be denied.");
|
||||
done();
|
||||
});
|
||||
},
|
||||
|
||||
function (done) {
|
||||
info("Test requestPermission grant");
|
||||
NotificationTest.allowNotifications();
|
||||
Notification.requestPermission(function (perm) {
|
||||
is(perm, "granted", "Permission should be granted.");
|
||||
is(Notification.permission, "granted", "Permission should be granted");
|
||||
done();
|
||||
});
|
||||
},
|
||||
|
||||
function () {
|
||||
info("Test invalid requestPermission");
|
||||
try {
|
||||
Notification.requestPermission({});
|
||||
ok(false, "Non callable arg to requestPermission should throw");
|
||||
} catch (e) {
|
||||
ok(true, "Non callable arg to requestPermission should throw");
|
||||
}
|
||||
},
|
||||
|
||||
function (done) {
|
||||
info("Test create notification");
|
||||
|
||||
var options = {
|
||||
dir: "auto",
|
||||
lang: "",
|
||||
body: "This is a notification body",
|
||||
tag: "sometag",
|
||||
icon: "icon.png"
|
||||
};
|
||||
var notification = new Notification("This is a title", options);
|
||||
|
||||
ok(notification, "Notification exists");
|
||||
is(notification.onclick, null, "onclick() should be null");
|
||||
is(notification.onshow, null, "onshow() should be null");
|
||||
is(notification.onerror, null, "onerror() should be null");
|
||||
is(notification.onclose, null, "onclose() should be null");
|
||||
is(typeof notification.close, "function", "close() should exist");
|
||||
|
||||
is(notification.dir, options.dir, "auto should get set");
|
||||
is(notification.lang, options.lang, "lang should get set");
|
||||
is(notification.body, options.body, "body should get set");
|
||||
is(notification.tag, options.tag, "tag should get set");
|
||||
is(notification.icon, options.icon, "icon should get set");
|
||||
|
||||
// store notification in test context
|
||||
this.notification = notification;
|
||||
|
||||
notification.onshow = function () {
|
||||
ok(true, "onshow handler should be called");
|
||||
done();
|
||||
};
|
||||
},
|
||||
|
||||
function (done) {
|
||||
info("Test closing a notification");
|
||||
var notification = this.notification;
|
||||
|
||||
notification.onclose = function () {
|
||||
ok(true, "onclose handler should be called");
|
||||
done();
|
||||
};
|
||||
|
||||
notification.close();
|
||||
},
|
||||
];
|
||||
|
||||
MockServices.register();
|
||||
NotificationTest.run(steps, function () {
|
||||
MockServices.unregister();
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,132 @@
|
|||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<head>
|
||||
<title>Notification Basics</title>
|
||||
<script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
|
||||
<script type="text/javascript" src="MockServices.js"></script>
|
||||
<script type="text/javascript" src="NotificationTest.js"></script>
|
||||
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
|
||||
</head>
|
||||
<body>
|
||||
<p id="display"></p>
|
||||
<div id="content" style="display: none"></div>
|
||||
<pre id="test"></pre>
|
||||
<script type="text/javascript">
|
||||
|
||||
function deleteAllNotifications() {
|
||||
var promise = Notification.get();
|
||||
promise.then(function (notifications) {
|
||||
notifications.forEach(function(notification) {
|
||||
notification.close();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
var info = NotificationTest.info;
|
||||
|
||||
var steps = [
|
||||
function (done) {
|
||||
info("Test that Notifcation.get fulfills the promise");
|
||||
var promise = Notification.get();
|
||||
ok(promise.then, "should return a promise");
|
||||
|
||||
// Create a new notification to make sure
|
||||
// Notification.get() works while creating
|
||||
var notification = new Notification("this is a test");
|
||||
|
||||
promise.then(function () {
|
||||
ok(true, "promise should be fulfilled");
|
||||
done();
|
||||
});
|
||||
},
|
||||
|
||||
deleteAllNotifications,
|
||||
|
||||
function (done) {
|
||||
info("Test adding a notification, and making sure get returns it");
|
||||
NotificationTest.allowNotifications();
|
||||
var options = {
|
||||
dir: "auto",
|
||||
lang: "",
|
||||
body: "This is a notification body",
|
||||
tag: "sometag",
|
||||
icon: "icon.png"
|
||||
};
|
||||
var notification = new Notification("This is a title", options);
|
||||
var promise = Notification.get();
|
||||
promise.then(function (notifications) {
|
||||
ok(notifications.length, "should return notifications");
|
||||
for (var i = 0; i < notifications.length; i++) {
|
||||
var notification = notifications[i];
|
||||
if (notification.tag === options.tag) {
|
||||
ok(true, "should contain newly created notification");
|
||||
for (var key in options) {
|
||||
is(notification[key], options[key], key + " property should match");
|
||||
}
|
||||
notification.close();
|
||||
return;
|
||||
}
|
||||
}
|
||||
ok(false, "should contain newly created notification");
|
||||
notification.close();
|
||||
});
|
||||
notification.onclose = done;
|
||||
},
|
||||
|
||||
function (done) {
|
||||
info("Testing fetching notification by tag filter");
|
||||
var n1 = new Notification("title1", {tag: "tag1"});
|
||||
var n2 = new Notification("title2", {tag: "tag2"});
|
||||
var n3 = new Notification("title3", {tag: "tag3"});
|
||||
var promise = Notification.get({tag: "tag3"});
|
||||
promise.then(function (notifications) {
|
||||
var notification = notifications[0];
|
||||
is(notifications.length, 1, "should return 1 notification");
|
||||
is(notifications[0].title, "title3", "titles should match");
|
||||
is(notifications[0].tag, "tag3", "tags should match");
|
||||
var closeCount = 0;
|
||||
var waitForAll = function () {
|
||||
if (++closeCount >= 3) {
|
||||
done();
|
||||
}
|
||||
};
|
||||
n1.onclose = waitForAll;
|
||||
n2.onclose = waitForAll;
|
||||
n3.onclose = waitForAll;
|
||||
n1.close();
|
||||
n2.close();
|
||||
n3.close();
|
||||
});
|
||||
},
|
||||
|
||||
deleteAllNotifications,
|
||||
|
||||
function (done) {
|
||||
info("Testing fetching no notifications");
|
||||
var promise = Notification.get();
|
||||
promise.then(function (notifications) {
|
||||
is(notifications.length, 0, "should return 0 notifications");
|
||||
done();
|
||||
});
|
||||
},
|
||||
|
||||
function (done) {
|
||||
info("Testing fetching multiple notifications");
|
||||
var n1 = new Notification("title1");
|
||||
var n2 = new Notification("title2");
|
||||
var n3 = new Notification("title3");
|
||||
var promise = Notification.get();
|
||||
promise.then(function (notifications) {
|
||||
is(notifications.length, 3, "should return 2 notifications");
|
||||
done();
|
||||
});
|
||||
}
|
||||
];
|
||||
|
||||
MockServices.register();
|
||||
NotificationTest.run(steps, function () {
|
||||
MockServices.unregister();
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
|
@ -1,100 +0,0 @@
|
|||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<!--
|
||||
https://bugzilla.mozilla.org/show_bug.cgi?id=782211
|
||||
-->
|
||||
<head>
|
||||
<title>Bug 782211</title>
|
||||
<script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
|
||||
<script type="text/javascript" src="notification_common.js"></script>
|
||||
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
|
||||
</head>
|
||||
<body>
|
||||
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=782211">Bug 782211</a>
|
||||
<p id="display"></p>
|
||||
<div id="content" style="display: none">
|
||||
</div>
|
||||
<pre id="test">
|
||||
</pre>
|
||||
<script type="text/javascript">
|
||||
if (window.Notification) {
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
|
||||
function showNotifications() {
|
||||
// Make sure callback is called.
|
||||
Notification.requestPermission(function(perm) {
|
||||
is(perm, "granted", "Permission should be granted.");
|
||||
is(Notification.permission, "granted", "Permission should be granted.");
|
||||
callbackCalled();
|
||||
});
|
||||
|
||||
// Make sure nothing bad happens when requestPermission is called without a callback.
|
||||
Notification.requestPermission();
|
||||
|
||||
try {
|
||||
Notification.requestPermission({});
|
||||
ok(false, "Non callable arugment to request permission should throw exception.");
|
||||
} catch (ex) {
|
||||
ok(true, "Non callable arugment to request permission should throw exception.");
|
||||
}
|
||||
|
||||
var title = "This is a title";
|
||||
|
||||
var notification = new Notification(title);
|
||||
|
||||
is(notification.title, title, "Title should be set");
|
||||
is(notification.dir, "auto", "Dir should default to 'auto'");
|
||||
is(notification.lang, "", "Lang should not be set");
|
||||
is(notification.body, "", "Body should not be set");
|
||||
is(notification.tag, "", "Tag should not be set");
|
||||
|
||||
var options = {
|
||||
dir: "auto",
|
||||
lang: "",
|
||||
body: "This is a notification body",
|
||||
tag: "sometag"
|
||||
};
|
||||
|
||||
var notification = new Notification(title, options);
|
||||
|
||||
is(notification.title, title, "Title should be set");
|
||||
is(notification.dir, options.dir, "Dir should be set");
|
||||
is(notification.lang, options.lang, "Lang should be set");
|
||||
is(notification.body, options.body, "Body should be set");
|
||||
is(notification.tag, options.tag, "Tag should be set");
|
||||
|
||||
notification.onclose = function() {
|
||||
ok(true, "Notification should be closed.");
|
||||
callbackCalled();
|
||||
};
|
||||
|
||||
notification.onshow = function() {
|
||||
ok(true, "Notification should be shown.");
|
||||
notification.close();
|
||||
callbackCalled();
|
||||
};
|
||||
|
||||
notification.onerror = function() {
|
||||
ok(false, "Failed to show notification.");
|
||||
reset_notifications();
|
||||
SimpleTest.finish();
|
||||
};
|
||||
|
||||
var numCallbacksCalled = 0;
|
||||
|
||||
function callbackCalled() {
|
||||
numCallbacksCalled++;
|
||||
if (numCallbacksCalled == 3) {
|
||||
reset_notifications();
|
||||
SimpleTest.finish();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
setup_notifications(true, true, showNotifications);
|
||||
} else {
|
||||
ok(true, "Notifications are not enabled on the platform.");
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
|
@ -19,6 +19,9 @@ interface Notification : EventTarget {
|
|||
[Throws]
|
||||
static void requestPermission(optional NotificationPermissionCallback permissionCallback);
|
||||
|
||||
[Throws]
|
||||
static Promise get(optional GetNotificationOptions filter);
|
||||
|
||||
attribute EventHandler onclick;
|
||||
|
||||
attribute EventHandler onshow;
|
||||
|
@ -52,10 +55,14 @@ dictionary NotificationOptions {
|
|||
NotificationDirection dir = "auto";
|
||||
DOMString lang = "";
|
||||
DOMString body = "";
|
||||
DOMString tag;
|
||||
DOMString tag = "";
|
||||
DOMString icon = "";
|
||||
};
|
||||
|
||||
dictionary GetNotificationOptions {
|
||||
DOMString tag;
|
||||
};
|
||||
|
||||
enum NotificationPermission {
|
||||
"default",
|
||||
"denied",
|
||||
|
|
|
@ -18,6 +18,7 @@ Cu.import("resource://gre/modules/JNI.jsm");
|
|||
Cu.import('resource://gre/modules/Payment.jsm');
|
||||
Cu.import("resource://gre/modules/PermissionPromptHelper.jsm");
|
||||
Cu.import("resource://gre/modules/ContactService.jsm");
|
||||
Cu.import("resource://gre/modules/NotificationDB.jsm");
|
||||
|
||||
#ifdef ACCESSIBILITY
|
||||
Cu.import("resource://gre/modules/accessibility/AccessFu.jsm");
|
||||
|
|
|
@ -282,6 +282,8 @@
|
|||
@BINPATH@/components/ContactManager.manifest
|
||||
@BINPATH@/components/PhoneNumberService.js
|
||||
@BINPATH@/components/PhoneNumberService.manifest
|
||||
@BINPATH@/components/NotificationStorage.js
|
||||
@BINPATH@/components/NotificationStorage.manifest
|
||||
@BINPATH@/components/SettingsManager.js
|
||||
@BINPATH@/components/SettingsManager.manifest
|
||||
@BINPATH@/components/SettingsService.js
|
||||
|
|
Загрузка…
Ссылка в новой задаче