gecko/dom/src/notification/Notification.cpp

544 lines
16 KiB
C++
Raw Normal View History

/* 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 "PCOMContentPermissionRequestChild.h"
#include "mozilla/dom/Notification.h"
#include "mozilla/dom/OwningNonNull.h"
#include "mozilla/Preferences.h"
#include "TabChild.h"
#include "nsContentUtils.h"
#include "nsDOMEvent.h"
#include "nsIAlertsService.h"
#include "nsIContentPermissionPrompt.h"
#include "nsIDocument.h"
#include "nsIPermissionManager.h"
#include "nsServiceManagerUtils.h"
#include "nsToolkitCompsCID.h"
#include "nsGlobalWindow.h"
#include "nsDOMJSUtils.h"
#ifdef MOZ_B2G
#include "nsIDOMDesktopNotification.h"
#include "nsIAppsService.h"
#endif
namespace mozilla {
namespace dom {
class NotificationPermissionRequest : public nsIContentPermissionRequest,
public PCOMContentPermissionRequestChild,
public nsIRunnable
{
public:
NS_DECL_CYCLE_COLLECTING_ISUPPORTS
NS_DECL_NSICONTENTPERMISSIONREQUEST
NS_DECL_NSIRUNNABLE
NS_DECL_CYCLE_COLLECTION_CLASS_AMBIGUOUS(NotificationPermissionRequest,
nsIContentPermissionRequest)
NotificationPermissionRequest(nsIPrincipal* aPrincipal, nsPIDOMWindow* aWindow,
NotificationPermissionCallback* aCallback)
: mPrincipal(aPrincipal), mWindow(aWindow),
mPermission(NotificationPermission::Default),
mCallback(aCallback) {}
virtual ~NotificationPermissionRequest() {}
bool Recv__delete__(const bool& aAllow);
void IPDLRelease() { Release(); }
protected:
nsresult CallCallback();
nsresult DispatchCallback();
nsCOMPtr<nsIPrincipal> mPrincipal;
nsCOMPtr<nsPIDOMWindow> mWindow;
NotificationPermission mPermission;
nsRefPtr<NotificationPermissionCallback> mCallback;
};
class NotificationObserver : public nsIObserver
{
public:
NS_DECL_ISUPPORTS
NS_DECL_NSIOBSERVER
NotificationObserver(Notification* aNotification)
: mNotification(aNotification) {}
virtual ~NotificationObserver() {}
protected:
nsRefPtr<Notification> mNotification;
};
class NotificationTask : public nsIRunnable
{
public:
NS_DECL_ISUPPORTS
NS_DECL_NSIRUNNABLE
enum NotificationAction {
eShow,
eClose
};
NotificationTask(Notification* aNotification, NotificationAction aAction)
: mNotification(aNotification), mAction(aAction) {}
virtual ~NotificationTask() {}
protected:
nsRefPtr<Notification> mNotification;
NotificationAction mAction;
};
uint32_t Notification::sCount = 0;
NS_IMPL_CYCLE_COLLECTION_1(NotificationPermissionRequest, mWindow)
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(NotificationPermissionRequest)
NS_INTERFACE_MAP_ENTRY(nsIContentPermissionRequest)
NS_INTERFACE_MAP_ENTRY(nsIRunnable)
NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIContentPermissionRequest)
NS_INTERFACE_MAP_END
NS_IMPL_CYCLE_COLLECTING_ADDREF(NotificationPermissionRequest)
NS_IMPL_CYCLE_COLLECTING_RELEASE(NotificationPermissionRequest)
NS_IMETHODIMP
NotificationPermissionRequest::Run()
{
if (nsContentUtils::IsSystemPrincipal(mPrincipal)) {
mPermission = NotificationPermission::Granted;
} else {
// File are automatically granted permission.
nsCOMPtr<nsIURI> uri;
mPrincipal->GetURI(getter_AddRefs(uri));
if (uri) {
bool isFile;
uri->SchemeIs("file", &isFile);
if (isFile) {
mPermission = NotificationPermission::Granted;
}
}
}
// Grant permission if pref'ed on.
if (Preferences::GetBool("notification.prompt.testing", false)) {
if (Preferences::GetBool("notification.prompt.testing.allow", true)) {
mPermission = NotificationPermission::Granted;
} else {
mPermission = NotificationPermission::Denied;
}
}
if (mPermission != NotificationPermission::Default) {
return DispatchCallback();
}
if (XRE_GetProcessType() == GeckoProcessType_Content) {
// because owner implements nsITabChild, we can assume that it is
// the one and only TabChild.
TabChild* child = TabChild::GetFrom(mWindow->GetDocShell());
if (!child) {
return NS_ERROR_NOT_AVAILABLE;
}
// Retain a reference so the object isn't deleted without IPDL's knowledge.
// Corresponding release occurs in DeallocPContentPermissionRequest.
AddRef();
NS_NAMED_LITERAL_CSTRING(type, "desktop-notification");
NS_NAMED_LITERAL_CSTRING(access, "unused");
child->SendPContentPermissionRequestConstructor(this, type, access,
IPC::Principal(mPrincipal));
Sendprompt();
return NS_OK;
}
nsCOMPtr<nsIContentPermissionPrompt> prompt =
do_GetService(NS_CONTENT_PERMISSION_PROMPT_CONTRACTID);
if (prompt) {
prompt->Prompt(this);
}
return NS_OK;
}
NS_IMETHODIMP
NotificationPermissionRequest::GetPrincipal(nsIPrincipal** aRequestingPrincipal)
{
NS_ADDREF(*aRequestingPrincipal = mPrincipal);
return NS_OK;
}
NS_IMETHODIMP
NotificationPermissionRequest::GetWindow(nsIDOMWindow** aRequestingWindow)
{
NS_ADDREF(*aRequestingWindow = mWindow);
return NS_OK;
}
NS_IMETHODIMP
NotificationPermissionRequest::GetElement(nsIDOMElement** aElement)
{
NS_ENSURE_ARG_POINTER(aElement);
*aElement = nullptr;
return NS_OK;
}
NS_IMETHODIMP
NotificationPermissionRequest::Cancel()
{
mPermission = NotificationPermission::Denied;
return DispatchCallback();
}
NS_IMETHODIMP
NotificationPermissionRequest::Allow()
{
mPermission = NotificationPermission::Granted;
return DispatchCallback();
}
inline nsresult
NotificationPermissionRequest::DispatchCallback()
{
if (!mCallback) {
return NS_OK;
}
nsCOMPtr<nsIRunnable> callbackRunnable = NS_NewRunnableMethod(this,
&NotificationPermissionRequest::CallCallback);
return NS_DispatchToMainThread(callbackRunnable);
}
nsresult
NotificationPermissionRequest::CallCallback()
{
ErrorResult rv;
mCallback->Call(mPermission, rv);
return rv.ErrorCode();
}
NS_IMETHODIMP
NotificationPermissionRequest::GetAccess(nsACString& aAccess)
{
aAccess.AssignLiteral("unused");
return NS_OK;
}
NS_IMETHODIMP
NotificationPermissionRequest::GetType(nsACString& aType)
{
aType.AssignLiteral("desktop-notification");
return NS_OK;
}
bool
NotificationPermissionRequest::Recv__delete__(const bool& aAllow)
{
if (aAllow) {
(void) Allow();
} else {
(void) Cancel();
}
return true;
}
NS_IMPL_ISUPPORTS1(NotificationTask, nsIRunnable)
NS_IMETHODIMP
NotificationTask::Run()
{
switch (mAction) {
case eShow:
return mNotification->ShowInternal();
case eClose:
return mNotification->CloseInternal();
default:
MOZ_CRASH("Unexpected action for NotificationTask.");
}
}
NS_IMPL_ISUPPORTS1(NotificationObserver, nsIObserver)
NS_IMETHODIMP
NotificationObserver::Observe(nsISupports* aSubject, const char* aTopic,
const PRUnichar* aData)
{
if (!strcmp("alertclickcallback", aTopic)) {
mNotification->DispatchTrustedEvent(NS_LITERAL_STRING("click"));
} else if (!strcmp("alertfinished", aTopic)) {
mNotification->mIsClosed = true;
mNotification->DispatchTrustedEvent(NS_LITERAL_STRING("close"));
} else if (!strcmp("alertshow", aTopic)) {
mNotification->DispatchTrustedEvent(NS_LITERAL_STRING("show"));
}
return NS_OK;
}
Notification::Notification(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),
mTag(aTag), mIconUrl(aIconUrl), mIsClosed(false)
{
SetIsDOMBinding();
}
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);
nsCOMPtr<nsPIDOMWindow> window = do_QueryInterface(aGlobal.GetAsSupports());
MOZ_ASSERT(window, "Window should not be null.");
notification->BindToOwner(window);
// Queue a task to show the notification.
nsCOMPtr<nsIRunnable> showNotificationTask =
new NotificationTask(notification, NotificationTask::eShow);
NS_DispatchToMainThread(showNotificationTask);
return notification.forget();
}
nsresult
Notification::ShowInternal()
{
nsCOMPtr<nsIAlertsService> alertService =
do_GetService(NS_ALERTSERVICE_CONTRACTID);
ErrorResult result;
if (GetPermissionInternal(GetOwner(), result) !=
NotificationPermission::Granted || !alertService) {
// We do not have permission to show a notification or alert service
// is not available.
return DispatchTrustedEvent(NS_LITERAL_STRING("error"));
}
nsresult rv;
nsAutoString absoluteUrl;
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);
}
}
nsCOMPtr<nsIObserver> observer = new NotificationObserver(this);
nsString alertName;
rv = GetAlertName(alertName);
NS_ENSURE_SUCCESS(rv, rv);
#ifdef MOZ_B2G
nsCOMPtr<nsIAppNotificationService> appNotifier =
do_GetService("@mozilla.org/system-alerts-service;1");
if (appNotifier) {
nsCOMPtr<nsPIDOMWindow> window = GetOwner();
uint32_t appId = (window.get())->GetDoc()->NodePrincipal()->GetAppId();
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);
}
}
#endif
// In the case of IPC, the parent process uses the cookie to map to
// 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);
}
void
Notification::RequestPermission(const GlobalObject& aGlobal,
const Optional<OwningNonNull<NotificationPermissionCallback> >& aCallback,
ErrorResult& aRv)
{
// Get principal from global to make permission request for notifications.
nsCOMPtr<nsPIDOMWindow> window = do_QueryInterface(aGlobal.GetAsSupports());
nsCOMPtr<nsIScriptObjectPrincipal> sop = do_QueryInterface(aGlobal.GetAsSupports());
if (!sop) {
aRv.Throw(NS_ERROR_UNEXPECTED);
return;
}
nsCOMPtr<nsIPrincipal> principal = sop->GetPrincipal();
NotificationPermissionCallback* permissionCallback = nullptr;
if (aCallback.WasPassed()) {
permissionCallback = &aCallback.Value();
}
nsCOMPtr<nsIRunnable> request =
new NotificationPermissionRequest(principal, window, permissionCallback);
NS_DispatchToMainThread(request);
}
NotificationPermission
Notification::GetPermission(const GlobalObject& aGlobal, ErrorResult& aRv)
{
return GetPermissionInternal(aGlobal.GetAsSupports(), aRv);
}
NotificationPermission
Notification::GetPermissionInternal(nsISupports* aGlobal, ErrorResult& aRv)
{
// Get principal from global to check permission for notifications.
nsCOMPtr<nsIScriptObjectPrincipal> sop = do_QueryInterface(aGlobal);
if (!sop) {
aRv.Throw(NS_ERROR_UNEXPECTED);
return NotificationPermission::Denied;
}
nsCOMPtr<nsIPrincipal> principal = sop->GetPrincipal();
if (nsContentUtils::IsSystemPrincipal(principal)) {
return NotificationPermission::Granted;
} else {
// Allow files to show notifications by default.
nsCOMPtr<nsIURI> uri;
principal->GetURI(getter_AddRefs(uri));
if (uri) {
bool isFile;
uri->SchemeIs("file", &isFile);
if (isFile) {
return NotificationPermission::Granted;
}
}
}
// We also allow notifications is they are pref'ed on.
if (Preferences::GetBool("notification.prompt.testing", false)) {
if (Preferences::GetBool("notification.prompt.testing.allow", true)) {
return NotificationPermission::Granted;
} else {
return NotificationPermission::Denied;
}
}
uint32_t permission = nsIPermissionManager::UNKNOWN_ACTION;
nsCOMPtr<nsIPermissionManager> permissionManager =
do_GetService(NS_PERMISSIONMANAGER_CONTRACTID);
permissionManager->TestPermissionFromPrincipal(principal,
"desktop-notification",
&permission);
// Convert the result to one of the enum types.
switch (permission) {
case nsIPermissionManager::ALLOW_ACTION:
return NotificationPermission::Granted;
case nsIPermissionManager::DENY_ACTION:
return NotificationPermission::Denied;
default:
return NotificationPermission::Default;
}
}
bool
Notification::PrefEnabled()
{
return Preferences::GetBool("dom.webnotifications.enabled", false);
}
JSObject*
Notification::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aScope)
{
return mozilla::dom::NotificationBinding::Wrap(aCx, aScope, this);
}
void
Notification::Close()
{
// Queue a task to close the notification.
nsCOMPtr<nsIRunnable> showNotificationTask =
new NotificationTask(this, NotificationTask::eClose);
NS_DispatchToMainThread(showNotificationTask);
}
nsresult
Notification::CloseInternal()
{
if (!mIsClosed) {
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);
}
}
return NS_OK;
}
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);
NS_ENSURE_SUCCESS(rv, rv);
aAlertName.AppendLiteral("#");
aAlertName.Append(mTag);
return NS_OK;
}
} // namespace dom
} // namespace mozilla