diff options
Diffstat (limited to 'components/alerts/src')
-rw-r--r-- | components/alerts/src/AlertNotification.cpp | 361 | ||||
-rw-r--r-- | components/alerts/src/AlertNotification.h | 81 | ||||
-rw-r--r-- | components/alerts/src/AlertNotificationIPCSerializer.h | 122 | ||||
-rw-r--r-- | components/alerts/src/nsAlertsService.cpp | 301 | ||||
-rw-r--r-- | components/alerts/src/nsAlertsService.h | 31 | ||||
-rw-r--r-- | components/alerts/src/nsAlertsUtils.cpp | 43 | ||||
-rw-r--r-- | components/alerts/src/nsAlertsUtils.h | 32 | ||||
-rw-r--r-- | components/alerts/src/nsXULAlerts.cpp | 380 | ||||
-rw-r--r-- | components/alerts/src/nsXULAlerts.h | 84 |
9 files changed, 1435 insertions, 0 deletions
diff --git a/components/alerts/src/AlertNotification.cpp b/components/alerts/src/AlertNotification.cpp new file mode 100644 index 000000000..b828f1100 --- /dev/null +++ b/components/alerts/src/AlertNotification.cpp @@ -0,0 +1,361 @@ +/* This Source Code Form is subject to the terms of the Mozilla Pub + * License, v. 2.0. If a copy of the MPL was not distributed with t + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/AlertNotification.h" + +#include "imgIContainer.h" +#include "imgINotificationObserver.h" +#include "imgIRequest.h" +#include "imgLoader.h" +#include "nsAlertsUtils.h" +#include "nsComponentManagerUtils.h" +#include "nsContentUtils.h" +#include "nsNetUtil.h" +#include "nsServiceManagerUtils.h" + +#include "mozilla/Unused.h" + +namespace mozilla { + +NS_IMPL_ISUPPORTS(AlertNotification, nsIAlertNotification) + +AlertNotification::AlertNotification() + : mTextClickable(false) + , mInPrivateBrowsing(false) +{} + +AlertNotification::~AlertNotification() +{} + +NS_IMETHODIMP +AlertNotification::Init(const nsAString& aName, const nsAString& aImageURL, + const nsAString& aTitle, const nsAString& aText, + bool aTextClickable, const nsAString& aCookie, + const nsAString& aDir, const nsAString& aLang, + const nsAString& aData, nsIPrincipal* aPrincipal, + bool aInPrivateBrowsing, bool aRequireInteraction) +{ + mName = aName; + mImageURL = aImageURL; + mTitle = aTitle; + mText = aText; + mTextClickable = aTextClickable; + mCookie = aCookie; + mDir = aDir; + mLang = aLang; + mData = aData; + mPrincipal = aPrincipal; + mInPrivateBrowsing = aInPrivateBrowsing; + mRequireInteraction = aRequireInteraction; + return NS_OK; +} + +NS_IMETHODIMP +AlertNotification::GetName(nsAString& aName) +{ + aName = mName; + return NS_OK; +} + +NS_IMETHODIMP +AlertNotification::GetImageURL(nsAString& aImageURL) +{ + aImageURL = mImageURL; + return NS_OK; +} + +NS_IMETHODIMP +AlertNotification::GetTitle(nsAString& aTitle) +{ + aTitle = mTitle; + return NS_OK; +} + +NS_IMETHODIMP +AlertNotification::GetText(nsAString& aText) +{ + aText = mText; + return NS_OK; +} + +NS_IMETHODIMP +AlertNotification::GetTextClickable(bool* aTextClickable) +{ + *aTextClickable = mTextClickable; + return NS_OK; +} + +NS_IMETHODIMP +AlertNotification::GetCookie(nsAString& aCookie) +{ + aCookie = mCookie; + return NS_OK; +} + +NS_IMETHODIMP +AlertNotification::GetDir(nsAString& aDir) +{ + aDir = mDir; + return NS_OK; +} + +NS_IMETHODIMP +AlertNotification::GetLang(nsAString& aLang) +{ + aLang = mLang; + return NS_OK; +} + +NS_IMETHODIMP +AlertNotification::GetRequireInteraction(bool* aRequireInteraction) +{ + *aRequireInteraction = mRequireInteraction; + return NS_OK; +} + +NS_IMETHODIMP +AlertNotification::GetData(nsAString& aData) +{ + aData = mData; + return NS_OK; +} + +NS_IMETHODIMP +AlertNotification::GetPrincipal(nsIPrincipal** aPrincipal) +{ + NS_IF_ADDREF(*aPrincipal = mPrincipal); + return NS_OK; +} + +NS_IMETHODIMP +AlertNotification::GetURI(nsIURI** aURI) +{ + if (!nsAlertsUtils::IsActionablePrincipal(mPrincipal)) { + *aURI = nullptr; + return NS_OK; + } + return mPrincipal->GetURI(aURI); +} + +NS_IMETHODIMP +AlertNotification::GetInPrivateBrowsing(bool* aInPrivateBrowsing) +{ + *aInPrivateBrowsing = mInPrivateBrowsing; + return NS_OK; +} + +NS_IMETHODIMP +AlertNotification::GetActionable(bool* aActionable) +{ + *aActionable = nsAlertsUtils::IsActionablePrincipal(mPrincipal); + return NS_OK; +} + +NS_IMETHODIMP +AlertNotification::GetSource(nsAString& aSource) +{ + nsAlertsUtils::GetSourceHostPort(mPrincipal, aSource); + return NS_OK; +} + +NS_IMETHODIMP +AlertNotification::LoadImage(uint32_t aTimeout, + nsIAlertNotificationImageListener* aListener, + nsISupports* aUserData, + nsICancelable** aRequest) +{ + NS_ENSURE_ARG(aListener); + NS_ENSURE_ARG_POINTER(aRequest); + *aRequest = nullptr; + + // Exit early if this alert doesn't have an image. + if (mImageURL.IsEmpty()) { + return aListener->OnImageMissing(aUserData); + } + nsCOMPtr<nsIURI> imageURI; + NS_NewURI(getter_AddRefs(imageURI), mImageURL); + if (!imageURI) { + return aListener->OnImageMissing(aUserData); + } + + RefPtr<AlertImageRequest> request = new AlertImageRequest(imageURI, mPrincipal, + mInPrivateBrowsing, + aTimeout, aListener, + aUserData); + nsresult rv = request->Start(); + request.forget(aRequest); + return rv; +} + +NS_IMPL_CYCLE_COLLECTION(AlertImageRequest, mURI, mPrincipal, mListener, + mUserData) + +NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(AlertImageRequest) + NS_INTERFACE_MAP_ENTRY(imgINotificationObserver) + NS_INTERFACE_MAP_ENTRY(nsICancelable) + NS_INTERFACE_MAP_ENTRY(nsITimerCallback) + NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, imgINotificationObserver) +NS_INTERFACE_MAP_END + +NS_IMPL_CYCLE_COLLECTING_ADDREF(AlertImageRequest) +NS_IMPL_CYCLE_COLLECTING_RELEASE(AlertImageRequest) + +AlertImageRequest::AlertImageRequest(nsIURI* aURI, nsIPrincipal* aPrincipal, + bool aInPrivateBrowsing, uint32_t aTimeout, + nsIAlertNotificationImageListener* aListener, + nsISupports* aUserData) + : mURI(aURI) + , mPrincipal(aPrincipal) + , mInPrivateBrowsing(aInPrivateBrowsing) + , mTimeout(aTimeout) + , mListener(aListener) + , mUserData(aUserData) +{} + +AlertImageRequest::~AlertImageRequest() +{ + if (mRequest) { + mRequest->CancelAndForgetObserver(NS_BINDING_ABORTED); + } +} + +NS_IMETHODIMP +AlertImageRequest::Notify(imgIRequest* aRequest, int32_t aType, + const nsIntRect* aData) +{ + MOZ_ASSERT(aRequest == mRequest); + + uint32_t imgStatus = imgIRequest::STATUS_ERROR; + nsresult rv = aRequest->GetImageStatus(&imgStatus); + if (NS_WARN_IF(NS_FAILED(rv)) || + (imgStatus & imgIRequest::STATUS_ERROR)) { + return NotifyMissing(); + } + + // If the image is already decoded, `FRAME_COMPLETE` will fire before + // `LOAD_COMPLETE`, so we can notify the listener immediately. Otherwise, + // we'll need to request a decode when `LOAD_COMPLETE` fires, and wait + // for the first frame. + + if (aType == imgINotificationObserver::LOAD_COMPLETE) { + if (!(imgStatus & imgIRequest::STATUS_FRAME_COMPLETE)) { + nsCOMPtr<imgIContainer> image; + rv = aRequest->GetImage(getter_AddRefs(image)); + if (NS_WARN_IF(NS_FAILED(rv) || !image)) { + return NotifyMissing(); + } + + // Ask the image to decode at its intrinsic size. + int32_t width = 0, height = 0; + image->GetWidth(&width); + image->GetHeight(&height); + image->RequestDecodeForSize(gfx::IntSize(width, height), imgIContainer::FLAG_NONE); + } + return NS_OK; + } + + if (aType == imgINotificationObserver::FRAME_COMPLETE) { + return NotifyComplete(); + } + + return NS_OK; +} + +NS_IMETHODIMP +AlertImageRequest::Notify(nsITimer* aTimer) +{ + MOZ_ASSERT(aTimer == mTimer); + return NotifyMissing(); +} + +NS_IMETHODIMP +AlertImageRequest::Cancel(nsresult aReason) +{ + if (mRequest) { + mRequest->Cancel(aReason); + } + // We call `NotifyMissing` here because we won't receive a `LOAD_COMPLETE` + // notification if we cancel the request before it loads (bug 1233086, + // comment 33). Once that's fixed, `nsIAlertNotification::loadImage` could + // return the underlying `imgIRequest` instead of the wrapper. + return NotifyMissing(); +} + +nsresult +AlertImageRequest::Start() +{ + // Keep the request alive until we notify the image listener. + NS_ADDREF_THIS(); + + nsresult rv; + if (mTimeout > 0) { + mTimer = do_CreateInstance(NS_TIMER_CONTRACTID); + if (NS_WARN_IF(!mTimer)) { + return NotifyMissing(); + } + rv = mTimer->InitWithCallback(this, mTimeout, + nsITimer::TYPE_ONE_SHOT); + if (NS_WARN_IF(NS_FAILED(rv))) { + return NotifyMissing(); + } + } + + // Begin loading the image. + imgLoader* il = imgLoader::NormalLoader(); + if (!il) { + return NotifyMissing(); + } + + // Bug 1237405: `LOAD_ANONYMOUS` disables cookies, but we want to use a + // temporary cookie jar instead. We should also use + // `imgLoader::PrivateBrowsingLoader()` instead of the normal loader. + // Unfortunately, the PB loader checks the load group, and asserts if its + // load context's PB flag isn't set. The fix is to pass the load group to + // `nsIAlertNotification::loadImage`. + int32_t loadFlags = mInPrivateBrowsing ? nsIRequest::LOAD_ANONYMOUS : + nsIRequest::LOAD_NORMAL; + + rv = il->LoadImageXPCOM(mURI, nullptr, nullptr, + NS_LITERAL_STRING("default"), mPrincipal, nullptr, + this, nullptr, loadFlags, nullptr, + nsIContentPolicy::TYPE_INTERNAL_IMAGE, + getter_AddRefs(mRequest)); + if (NS_WARN_IF(NS_FAILED(rv))) { + return NotifyMissing(); + } + + return NS_OK; +} + +nsresult +AlertImageRequest::NotifyMissing() +{ + if (mTimer) { + mTimer->Cancel(); + mTimer = nullptr; + } + if (nsCOMPtr<nsIAlertNotificationImageListener> listener = mListener.forget()) { + nsresult rv = listener->OnImageMissing(mUserData); + NS_RELEASE_THIS(); + return rv; + } + return NS_OK; +} + +nsresult +AlertImageRequest::NotifyComplete() +{ + if (mTimer) { + mTimer->Cancel(); + mTimer = nullptr; + } + if (nsCOMPtr<nsIAlertNotificationImageListener> listener = mListener.forget()) { + nsresult rv = listener->OnImageReady(mUserData, mRequest); + NS_RELEASE_THIS(); + return rv; + } + return NS_OK; +} + +} // namespace mozilla diff --git a/components/alerts/src/AlertNotification.h b/components/alerts/src/AlertNotification.h new file mode 100644 index 000000000..c0bcc0ba9 --- /dev/null +++ b/components/alerts/src/AlertNotification.h @@ -0,0 +1,81 @@ +/* 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/. */ + +#ifndef mozilla_AlertNotification_h__ +#define mozilla_AlertNotification_h__ + +#include "imgINotificationObserver.h" +#include "nsIAlertsService.h" +#include "nsCOMPtr.h" +#include "nsCycleCollectionParticipant.h" +#include "nsICancelable.h" +#include "nsIPrincipal.h" +#include "nsString.h" +#include "nsITimer.h" + +namespace mozilla { + +class AlertImageRequest final : public imgINotificationObserver, + public nsICancelable, + public nsITimerCallback +{ +public: + NS_DECL_CYCLE_COLLECTING_ISUPPORTS + NS_DECL_CYCLE_COLLECTION_CLASS_AMBIGUOUS(AlertImageRequest, + imgINotificationObserver) + NS_DECL_IMGINOTIFICATIONOBSERVER + NS_DECL_NSICANCELABLE + NS_DECL_NSITIMERCALLBACK + + AlertImageRequest(nsIURI* aURI, nsIPrincipal* aPrincipal, + bool aInPrivateBrowsing, uint32_t aTimeout, + nsIAlertNotificationImageListener* aListener, + nsISupports* aUserData); + + nsresult Start(); + +private: + virtual ~AlertImageRequest(); + + nsresult NotifyMissing(); + nsresult NotifyComplete(); + + nsCOMPtr<nsIURI> mURI; + nsCOMPtr<nsIPrincipal> mPrincipal; + bool mInPrivateBrowsing; + uint32_t mTimeout; + nsCOMPtr<nsIAlertNotificationImageListener> mListener; + nsCOMPtr<nsISupports> mUserData; + nsCOMPtr<nsITimer> mTimer; + nsCOMPtr<imgIRequest> mRequest; +}; + +class AlertNotification final : public nsIAlertNotification +{ +public: + NS_DECL_ISUPPORTS + NS_DECL_NSIALERTNOTIFICATION + AlertNotification(); + +protected: + virtual ~AlertNotification(); + +private: + nsString mName; + nsString mImageURL; + nsString mTitle; + nsString mText; + bool mTextClickable; + nsString mCookie; + nsString mDir; + nsString mLang; + bool mRequireInteraction; + nsString mData; + nsCOMPtr<nsIPrincipal> mPrincipal; + bool mInPrivateBrowsing; +}; + +} // namespace mozilla + +#endif /* mozilla_AlertNotification_h__ */ diff --git a/components/alerts/src/AlertNotificationIPCSerializer.h b/components/alerts/src/AlertNotificationIPCSerializer.h new file mode 100644 index 000000000..9544f9633 --- /dev/null +++ b/components/alerts/src/AlertNotificationIPCSerializer.h @@ -0,0 +1,122 @@ +/* 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/. */ + +#ifndef mozilla_AlertNotificationIPCSerializer_h__ +#define mozilla_AlertNotificationIPCSerializer_h__ + +#include "nsComponentManagerUtils.h" +#include "nsCOMPtr.h" +#include "nsIAlertsService.h" +#include "nsIPrincipal.h" +#include "nsString.h" + +#include "ipc/IPCMessageUtils.h" + +#include "mozilla/dom/PermissionMessageUtils.h" + +typedef nsIAlertNotification* AlertNotificationType; + +namespace IPC { + +template <> +struct ParamTraits<AlertNotificationType> +{ + typedef AlertNotificationType paramType; + + static void Write(Message* aMsg, const paramType& aParam) + { + bool isNull = !aParam; + if (isNull) { + WriteParam(aMsg, isNull); + return; + } + + nsString name, imageURL, title, text, cookie, dir, lang, data; + bool textClickable, inPrivateBrowsing, requireInteraction; + nsCOMPtr<nsIPrincipal> principal; + + if (NS_WARN_IF(NS_FAILED(aParam->GetName(name))) || + NS_WARN_IF(NS_FAILED(aParam->GetImageURL(imageURL))) || + NS_WARN_IF(NS_FAILED(aParam->GetTitle(title))) || + NS_WARN_IF(NS_FAILED(aParam->GetText(text))) || + NS_WARN_IF(NS_FAILED(aParam->GetTextClickable(&textClickable))) || + NS_WARN_IF(NS_FAILED(aParam->GetCookie(cookie))) || + NS_WARN_IF(NS_FAILED(aParam->GetDir(dir))) || + NS_WARN_IF(NS_FAILED(aParam->GetLang(lang))) || + NS_WARN_IF(NS_FAILED(aParam->GetData(data))) || + NS_WARN_IF(NS_FAILED(aParam->GetPrincipal(getter_AddRefs(principal)))) || + NS_WARN_IF(NS_FAILED(aParam->GetInPrivateBrowsing(&inPrivateBrowsing))) || + NS_WARN_IF(NS_FAILED(aParam->GetRequireInteraction(&requireInteraction)))) { + + // Write a `null` object if any getter returns an error. Otherwise, the + // receiver will try to deserialize an incomplete object and crash. + WriteParam(aMsg, /* isNull */ true); + return; + } + + WriteParam(aMsg, isNull); + WriteParam(aMsg, name); + WriteParam(aMsg, imageURL); + WriteParam(aMsg, title); + WriteParam(aMsg, text); + WriteParam(aMsg, textClickable); + WriteParam(aMsg, cookie); + WriteParam(aMsg, dir); + WriteParam(aMsg, lang); + WriteParam(aMsg, data); + WriteParam(aMsg, IPC::Principal(principal)); + WriteParam(aMsg, inPrivateBrowsing); + WriteParam(aMsg, requireInteraction); + } + + static bool Read(const Message* aMsg, PickleIterator* aIter, paramType* aResult) + { + bool isNull; + NS_ENSURE_TRUE(ReadParam(aMsg, aIter, &isNull), false); + if (isNull) { + *aResult = nullptr; + return true; + } + + nsString name, imageURL, title, text, cookie, dir, lang, data; + bool textClickable, inPrivateBrowsing, requireInteraction; + IPC::Principal principal; + + if (!ReadParam(aMsg, aIter, &name) || + !ReadParam(aMsg, aIter, &imageURL) || + !ReadParam(aMsg, aIter, &title) || + !ReadParam(aMsg, aIter, &text) || + !ReadParam(aMsg, aIter, &textClickable) || + !ReadParam(aMsg, aIter, &cookie) || + !ReadParam(aMsg, aIter, &dir) || + !ReadParam(aMsg, aIter, &lang) || + !ReadParam(aMsg, aIter, &data) || + !ReadParam(aMsg, aIter, &principal) || + !ReadParam(aMsg, aIter, &inPrivateBrowsing) || + !ReadParam(aMsg, aIter, &requireInteraction)) { + + return false; + } + + nsCOMPtr<nsIAlertNotification> alert = + do_CreateInstance(ALERT_NOTIFICATION_CONTRACTID); + if (NS_WARN_IF(!alert)) { + *aResult = nullptr; + return true; + } + nsresult rv = alert->Init(name, imageURL, title, text, textClickable, + cookie, dir, lang, data, principal, + inPrivateBrowsing, requireInteraction); + if (NS_WARN_IF(NS_FAILED(rv))) { + *aResult = nullptr; + return true; + } + alert.forget(aResult); + return true; + } +}; + +} // namespace IPC + +#endif /* mozilla_AlertNotificationIPCSerializer_h__ */ diff --git a/components/alerts/src/nsAlertsService.cpp b/components/alerts/src/nsAlertsService.cpp new file mode 100644 index 000000000..4f248c861 --- /dev/null +++ b/components/alerts/src/nsAlertsService.cpp @@ -0,0 +1,301 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode:nil; c-basic-offset: 2 -*- */ +/* 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 "mozilla/dom/ContentChild.h" +#include "mozilla/dom/PermissionMessageUtils.h" +#include "mozilla/Preferences.h" +#include "nsXULAppAPI.h" + +#include "nsAlertsService.h" + +#include "nsXPCOM.h" +#include "nsIServiceManager.h" +#include "nsIDOMWindow.h" +#include "nsPromiseFlatString.h" +#include "nsToolkitCompsCID.h" + +#ifdef MOZ_PLACES +#include "mozIAsyncFavicons.h" +#include "nsIFaviconService.h" +#endif // MOZ_PLACES + +#ifdef XP_WIN +#include <shellapi.h> +#endif + +using namespace mozilla; + +using mozilla::dom::ContentChild; + +namespace { + +#ifdef MOZ_PLACES + +class IconCallback final : public nsIFaviconDataCallback +{ +public: + NS_DECL_ISUPPORTS + + IconCallback(nsIAlertsService* aBackend, + nsIAlertNotification* aAlert, + nsIObserver* aAlertListener) + : mBackend(aBackend) + , mAlert(aAlert) + , mAlertListener(aAlertListener) + {} + + NS_IMETHOD + OnComplete(nsIURI *aIconURI, uint32_t aIconSize, const uint8_t *aIconData, + const nsACString &aMimeType) override + { + nsresult rv = NS_ERROR_FAILURE; + if (aIconSize > 0) { + nsCOMPtr<nsIAlertsIconData> alertsIconData(do_QueryInterface(mBackend)); + if (alertsIconData) { + rv = alertsIconData->ShowAlertWithIconData(mAlert, mAlertListener, + aIconSize, aIconData); + } + } else if (aIconURI) { + nsCOMPtr<nsIAlertsIconURI> alertsIconURI(do_QueryInterface(mBackend)); + if (alertsIconURI) { + rv = alertsIconURI->ShowAlertWithIconURI(mAlert, mAlertListener, + aIconURI); + } + } + if (NS_FAILED(rv)) { + rv = mBackend->ShowAlert(mAlert, mAlertListener); + } + return rv; + } + +private: + virtual ~IconCallback() {} + + nsCOMPtr<nsIAlertsService> mBackend; + nsCOMPtr<nsIAlertNotification> mAlert; + nsCOMPtr<nsIObserver> mAlertListener; +}; + +NS_IMPL_ISUPPORTS(IconCallback, nsIFaviconDataCallback) + +#endif // MOZ_PLACES + +nsresult +ShowWithIconBackend(nsIAlertsService* aBackend, nsIAlertNotification* aAlert, + nsIObserver* aAlertListener) +{ +#ifdef MOZ_PLACES + nsCOMPtr<nsIURI> uri; + nsresult rv = aAlert->GetURI(getter_AddRefs(uri)); + if (NS_FAILED(rv) || !uri) { + return NS_ERROR_FAILURE; + } + + // Ensure the backend supports favicons. + nsCOMPtr<nsIAlertsIconData> alertsIconData(do_QueryInterface(aBackend)); + nsCOMPtr<nsIAlertsIconURI> alertsIconURI; + if (!alertsIconData) { + alertsIconURI = do_QueryInterface(aBackend); + } + if (!alertsIconData && !alertsIconURI) { + return NS_ERROR_NOT_IMPLEMENTED; + } + + nsCOMPtr<mozIAsyncFavicons> favicons(do_GetService( + "@mozilla.org/browser/favicon-service;1")); + NS_ENSURE_TRUE(favicons, NS_ERROR_FAILURE); + + nsCOMPtr<nsIFaviconDataCallback> callback = + new IconCallback(aBackend, aAlert, aAlertListener); + if (alertsIconData) { + return favicons->GetFaviconDataForPage(uri, callback); + } + return favicons->GetFaviconURLForPage(uri, callback); +#else + return NS_ERROR_NOT_IMPLEMENTED; +#endif // !MOZ_PLACES +} + +nsresult +ShowWithBackend(nsIAlertsService* aBackend, nsIAlertNotification* aAlert, + nsIObserver* aAlertListener, const nsAString& aPersistentData) +{ + if (!aPersistentData.IsEmpty()) { + return aBackend->ShowPersistentNotification( + aPersistentData, aAlert, aAlertListener); + } + + if (Preferences::GetBool("alerts.showFavicons")) { + nsresult rv = ShowWithIconBackend(aBackend, aAlert, aAlertListener); + if (NS_SUCCEEDED(rv)) { + return rv; + } + } + + // If favicons are disabled, or the backend doesn't support them, show the + // alert without one. + return aBackend->ShowAlert(aAlert, aAlertListener); +} + +} // anonymous namespace + +NS_IMPL_ISUPPORTS(nsAlertsService, nsIAlertsService, nsIAlertsDoNotDisturb) + +nsAlertsService::nsAlertsService() : + mBackend(nullptr) +{ + mBackend = do_GetService(NS_SYSTEMALERTSERVICE_CONTRACTID); +} + +nsAlertsService::~nsAlertsService() +{} + +bool nsAlertsService::ShouldShowAlert() +{ + bool result = true; + +#ifdef XP_WIN + QUERY_USER_NOTIFICATION_STATE qstate; + if (SUCCEEDED(SHQueryUserNotificationState(&qstate))) { + if (qstate != QUNS_ACCEPTS_NOTIFICATIONS) { + result = false; + } + } +#endif + + return result; +} + +NS_IMETHODIMP nsAlertsService::ShowAlertNotification(const nsAString & aImageUrl, const nsAString & aAlertTitle, + const nsAString & aAlertText, bool aAlertTextClickable, + const nsAString & aAlertCookie, + nsIObserver * aAlertListener, + const nsAString & aAlertName, + const nsAString & aBidi, + const nsAString & aLang, + const nsAString & aData, + nsIPrincipal * aPrincipal, + bool aInPrivateBrowsing, + bool aRequireInteraction) +{ + nsCOMPtr<nsIAlertNotification> alert = + do_CreateInstance(ALERT_NOTIFICATION_CONTRACTID); + NS_ENSURE_TRUE(alert, NS_ERROR_FAILURE); + nsresult rv = alert->Init(aAlertName, aImageUrl, aAlertTitle, + aAlertText, aAlertTextClickable, + aAlertCookie, aBidi, aLang, aData, + aPrincipal, aInPrivateBrowsing, + aRequireInteraction); + NS_ENSURE_SUCCESS(rv, rv); + return ShowAlert(alert, aAlertListener); +} + + +NS_IMETHODIMP nsAlertsService::ShowAlert(nsIAlertNotification * aAlert, + nsIObserver * aAlertListener) +{ + return ShowPersistentNotification(EmptyString(), aAlert, aAlertListener); +} + +NS_IMETHODIMP nsAlertsService::ShowPersistentNotification(const nsAString & aPersistentData, + nsIAlertNotification * aAlert, + nsIObserver * aAlertListener) +{ + NS_ENSURE_ARG(aAlert); + + nsAutoString cookie; + nsresult rv = aAlert->GetCookie(cookie); + NS_ENSURE_SUCCESS(rv, rv); + + if (XRE_IsContentProcess()) { + ContentChild* cpc = ContentChild::GetSingleton(); + + if (aAlertListener) + cpc->AddRemoteAlertObserver(cookie, aAlertListener); + + cpc->SendShowAlert(aAlert); + return NS_OK; + } + + // Check if there is an optional service that handles system-level notifications + if (mBackend) { + rv = ShowWithBackend(mBackend, aAlert, aAlertListener, aPersistentData); + if (NS_SUCCEEDED(rv)) { + return rv; + } + // If the system backend failed to show the alert, clear the backend and + // retry with XUL notifications. Future alerts will always use XUL. + mBackend = nullptr; + } + + if (!ShouldShowAlert()) { + // Do not display the alert. Instead call alertfinished and get out. + if (aAlertListener) + aAlertListener->Observe(nullptr, "alertfinished", cookie.get()); + return NS_OK; + } + + // Use XUL notifications as a fallback if above methods have failed. + nsCOMPtr<nsIAlertsService> xulBackend(nsXULAlerts::GetInstance()); + NS_ENSURE_TRUE(xulBackend, NS_ERROR_FAILURE); + return ShowWithBackend(xulBackend, aAlert, aAlertListener, aPersistentData); +} + +NS_IMETHODIMP nsAlertsService::CloseAlert(const nsAString& aAlertName, + nsIPrincipal* aPrincipal) +{ + if (XRE_IsContentProcess()) { + ContentChild* cpc = ContentChild::GetSingleton(); + cpc->SendCloseAlert(nsAutoString(aAlertName), IPC::Principal(aPrincipal)); + return NS_OK; + } + + nsresult rv; + // Try the system notification service. + if (mBackend) { + rv = mBackend->CloseAlert(aAlertName, aPrincipal); + if (NS_WARN_IF(NS_FAILED(rv))) { + // If the system backend failed to close the alert, fall back to XUL for + // future alerts. + mBackend = nullptr; + } + } else { + nsCOMPtr<nsIAlertsService> xulBackend(nsXULAlerts::GetInstance()); + NS_ENSURE_TRUE(xulBackend, NS_ERROR_FAILURE); + rv = xulBackend->CloseAlert(aAlertName, aPrincipal); + } + return rv; +} + + +// nsIAlertsDoNotDisturb +NS_IMETHODIMP nsAlertsService::GetManualDoNotDisturb(bool* aRetVal) +{ + nsCOMPtr<nsIAlertsDoNotDisturb> alertsDND(GetDNDBackend()); + NS_ENSURE_TRUE(alertsDND, NS_ERROR_NOT_IMPLEMENTED); + return alertsDND->GetManualDoNotDisturb(aRetVal); +} + +NS_IMETHODIMP nsAlertsService::SetManualDoNotDisturb(bool aDoNotDisturb) +{ + nsCOMPtr<nsIAlertsDoNotDisturb> alertsDND(GetDNDBackend()); + NS_ENSURE_TRUE(alertsDND, NS_ERROR_NOT_IMPLEMENTED); + + nsresult rv = alertsDND->SetManualDoNotDisturb(aDoNotDisturb); + return rv; +} + +already_AddRefed<nsIAlertsDoNotDisturb> +nsAlertsService::GetDNDBackend() +{ + // Try the system notification service. + nsCOMPtr<nsIAlertsService> backend = mBackend; + if (!backend) { + backend = nsXULAlerts::GetInstance(); + } + + nsCOMPtr<nsIAlertsDoNotDisturb> alertsDND(do_QueryInterface(backend)); + return alertsDND.forget(); +} diff --git a/components/alerts/src/nsAlertsService.h b/components/alerts/src/nsAlertsService.h new file mode 100644 index 000000000..d2b2e1e6c --- /dev/null +++ b/components/alerts/src/nsAlertsService.h @@ -0,0 +1,31 @@ +// /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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/. */ + +#ifndef nsAlertsService_h__ +#define nsAlertsService_h__ + +#include "nsIAlertsService.h" +#include "nsCOMPtr.h" +#include "nsXULAlerts.h" + +class nsAlertsService : public nsIAlertsService, + public nsIAlertsDoNotDisturb +{ +public: + NS_DECL_NSIALERTSDONOTDISTURB + NS_DECL_NSIALERTSSERVICE + NS_DECL_ISUPPORTS + + nsAlertsService(); + +protected: + virtual ~nsAlertsService(); + + bool ShouldShowAlert(); + already_AddRefed<nsIAlertsDoNotDisturb> GetDNDBackend(); + nsCOMPtr<nsIAlertsService> mBackend; +}; + +#endif /* nsAlertsService_h__ */ diff --git a/components/alerts/src/nsAlertsUtils.cpp b/components/alerts/src/nsAlertsUtils.cpp new file mode 100644 index 000000000..5f7d92d2a --- /dev/null +++ b/components/alerts/src/nsAlertsUtils.cpp @@ -0,0 +1,43 @@ +/* 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 "nsAlertsUtils.h" + +#include "nsCOMPtr.h" +#include "nsContentUtils.h" +#include "nsIStringBundle.h" +#include "nsIURI.h" +#include "nsXPIDLString.h" + +/* static */ +bool +nsAlertsUtils::IsActionablePrincipal(nsIPrincipal* aPrincipal) +{ + return aPrincipal && + !nsContentUtils::IsSystemOrExpandedPrincipal(aPrincipal) && + !aPrincipal->GetIsNullPrincipal(); +} + +/* static */ +void +nsAlertsUtils::GetSourceHostPort(nsIPrincipal* aPrincipal, + nsAString& aHostPort) +{ + if (!IsActionablePrincipal(aPrincipal)) { + return; + } + nsCOMPtr<nsIURI> principalURI; + if (NS_WARN_IF(NS_FAILED( + aPrincipal->GetURI(getter_AddRefs(principalURI))))) { + return; + } + if (!principalURI) { + return; + } + nsAutoCString hostPort; + if (NS_WARN_IF(NS_FAILED(principalURI->GetHostPort(hostPort)))) { + return; + } + CopyUTF8toUTF16(hostPort, aHostPort); +} diff --git a/components/alerts/src/nsAlertsUtils.h b/components/alerts/src/nsAlertsUtils.h new file mode 100644 index 000000000..bc11f6351 --- /dev/null +++ b/components/alerts/src/nsAlertsUtils.h @@ -0,0 +1,32 @@ +/* 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/. */ + +#ifndef nsAlertsUtils_h +#define nsAlertsUtils_h + +#include "nsIPrincipal.h" +#include "nsString.h" + +class nsAlertsUtils final +{ +private: + nsAlertsUtils() = delete; + +public: + /** + * Indicates whether an alert from |aPrincipal| should include the source + * string and action buttons. Returns false if |aPrincipal| is |nullptr|, or + * a system, expanded, or null principal. + */ + static bool + IsActionablePrincipal(nsIPrincipal* aPrincipal); + + /** + * Sets |aHostPort| to the host and port from |aPrincipal|'s URI, or an + * empty string if |aPrincipal| is not actionable. + */ + static void + GetSourceHostPort(nsIPrincipal* aPrincipal, nsAString& aHostPort); +}; +#endif /* nsAlertsUtils_h */ diff --git a/components/alerts/src/nsXULAlerts.cpp b/components/alerts/src/nsXULAlerts.cpp new file mode 100644 index 000000000..47e72a27f --- /dev/null +++ b/components/alerts/src/nsXULAlerts.cpp @@ -0,0 +1,380 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode:nil; c-basic-offset: 2 -*- */ +/* 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 "nsXULAlerts.h" + +#include "nsArray.h" +#include "nsComponentManagerUtils.h" +#include "nsCOMPtr.h" +#include "mozilla/ClearOnShutdown.h" +#include "mozilla/LookAndFeel.h" +#include "mozilla/dom/Notification.h" +#include "mozilla/Unused.h" +#include "nsIServiceManager.h" +#include "nsISupportsPrimitives.h" +#include "nsPIDOMWindow.h" +#include "nsIWindowWatcher.h" + +using namespace mozilla; + +#define ALERT_CHROME_URL "chrome://global/content/alerts/alert.xul" + +namespace { +StaticRefPtr<nsXULAlerts> gXULAlerts; +} // anonymous namespace + +NS_IMPL_CYCLE_COLLECTION(nsXULAlertObserver, mAlertWindow) + +NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsXULAlertObserver) + NS_INTERFACE_MAP_ENTRY(nsIObserver) + NS_INTERFACE_MAP_ENTRY(nsISupports) +NS_INTERFACE_MAP_END + +NS_IMPL_CYCLE_COLLECTING_ADDREF(nsXULAlertObserver) +NS_IMPL_CYCLE_COLLECTING_RELEASE(nsXULAlertObserver) + +NS_IMETHODIMP +nsXULAlertObserver::Observe(nsISupports* aSubject, const char* aTopic, + const char16_t* aData) +{ + if (!strcmp("alertfinished", aTopic)) { + mozIDOMWindowProxy* currentAlert = mXULAlerts->mNamedWindows.GetWeak(mAlertName); + // The window in mNamedWindows might be a replacement, thus it should only + // be removed if it is the same window that is associated with this listener. + if (currentAlert == mAlertWindow) { + mXULAlerts->mNamedWindows.Remove(mAlertName); + + if (mIsPersistent) { + mXULAlerts->PersistentAlertFinished(); + } + } + } + + nsresult rv = NS_OK; + if (mObserver) { + rv = mObserver->Observe(aSubject, aTopic, aData); + } + return rv; +} + +// We don't cycle collect nsXULAlerts since gXULAlerts will keep the instance +// alive till shutdown anyway. +NS_IMPL_ISUPPORTS(nsXULAlerts, nsIAlertsService, nsIAlertsDoNotDisturb, nsIAlertsIconURI) + +/* static */ already_AddRefed<nsXULAlerts> +nsXULAlerts::GetInstance() +{ + if (!gXULAlerts) { + gXULAlerts = new nsXULAlerts(); + ClearOnShutdown(&gXULAlerts); + } + RefPtr<nsXULAlerts> instance = gXULAlerts.get(); + return instance.forget(); +} + +void +nsXULAlerts::PersistentAlertFinished() +{ + MOZ_ASSERT(mPersistentAlertCount); + mPersistentAlertCount--; + + // Show next pending persistent alert if any. + if (!mPendingPersistentAlerts.IsEmpty()) { + ShowAlertWithIconURI(mPendingPersistentAlerts[0].mAlert, + mPendingPersistentAlerts[0].mListener, + nullptr); + mPendingPersistentAlerts.RemoveElementAt(0); + } +} + +NS_IMETHODIMP +nsXULAlerts::ShowAlertNotification(const nsAString& aImageUrl, const nsAString& aAlertTitle, + const nsAString& aAlertText, bool aAlertTextClickable, + const nsAString& aAlertCookie, nsIObserver* aAlertListener, + const nsAString& aAlertName, const nsAString& aBidi, + const nsAString& aLang, const nsAString& aData, + nsIPrincipal* aPrincipal, bool aInPrivateBrowsing, + bool aRequireInteraction) +{ + nsCOMPtr<nsIAlertNotification> alert = + do_CreateInstance(ALERT_NOTIFICATION_CONTRACTID); + NS_ENSURE_TRUE(alert, NS_ERROR_FAILURE); + nsresult rv = alert->Init(aAlertName, aImageUrl, aAlertTitle, + aAlertText, aAlertTextClickable, + aAlertCookie, aBidi, aLang, aData, + aPrincipal, aInPrivateBrowsing, + aRequireInteraction); + NS_ENSURE_SUCCESS(rv, rv); + return ShowAlert(alert, aAlertListener); +} + +NS_IMETHODIMP +nsXULAlerts::ShowPersistentNotification(const nsAString& aPersistentData, + nsIAlertNotification* aAlert, + nsIObserver* aAlertListener) +{ + return ShowAlert(aAlert, aAlertListener); +} + +NS_IMETHODIMP +nsXULAlerts::ShowAlert(nsIAlertNotification* aAlert, + nsIObserver* aAlertListener) +{ + nsAutoString name; + nsresult rv = aAlert->GetName(name); + NS_ENSURE_SUCCESS(rv, rv); + + // If there is a pending alert with the same name in the list of + // pending alerts, replace it. + if (!mPendingPersistentAlerts.IsEmpty()) { + for (uint32_t i = 0; i < mPendingPersistentAlerts.Length(); i++) { + nsAutoString pendingAlertName; + nsCOMPtr<nsIAlertNotification> pendingAlert = mPendingPersistentAlerts[i].mAlert; + rv = pendingAlert->GetName(pendingAlertName); + NS_ENSURE_SUCCESS(rv, rv); + + if (pendingAlertName.Equals(name)) { + nsAutoString cookie; + rv = pendingAlert->GetCookie(cookie); + NS_ENSURE_SUCCESS(rv, rv); + + if (mPendingPersistentAlerts[i].mListener) { + rv = mPendingPersistentAlerts[i].mListener->Observe(nullptr, "alertfinished", cookie.get()); + NS_ENSURE_SUCCESS(rv, rv); + } + + mPendingPersistentAlerts[i].Init(aAlert, aAlertListener); + return NS_OK; + } + } + } + + bool requireInteraction; + rv = aAlert->GetRequireInteraction(&requireInteraction); + NS_ENSURE_SUCCESS(rv, rv); + + if (requireInteraction && + !mNamedWindows.Contains(name) && + static_cast<int32_t>(mPersistentAlertCount) >= + Preferences::GetInt("dom.webnotifications.requireinteraction.count", 0)) { + PendingAlert* pa = mPendingPersistentAlerts.AppendElement(); + pa->Init(aAlert, aAlertListener); + return NS_OK; + } else { + return ShowAlertWithIconURI(aAlert, aAlertListener, nullptr); + } +} + +NS_IMETHODIMP +nsXULAlerts::ShowAlertWithIconURI(nsIAlertNotification* aAlert, + nsIObserver* aAlertListener, + nsIURI* aIconURI) +{ + bool inPrivateBrowsing; + nsresult rv = aAlert->GetInPrivateBrowsing(&inPrivateBrowsing); + NS_ENSURE_SUCCESS(rv, rv); + + nsAutoString cookie; + rv = aAlert->GetCookie(cookie); + NS_ENSURE_SUCCESS(rv, rv); + + if (mDoNotDisturb) { + if (aAlertListener) + aAlertListener->Observe(nullptr, "alertfinished", cookie.get()); + return NS_OK; + } + + nsAutoString name; + rv = aAlert->GetName(name); + NS_ENSURE_SUCCESS(rv, rv); + + nsAutoString imageUrl; + rv = aAlert->GetImageURL(imageUrl); + NS_ENSURE_SUCCESS(rv, rv); + + nsAutoString title; + rv = aAlert->GetTitle(title); + NS_ENSURE_SUCCESS(rv, rv); + + nsAutoString text; + rv = aAlert->GetText(text); + NS_ENSURE_SUCCESS(rv, rv); + + bool textClickable; + rv = aAlert->GetTextClickable(&textClickable); + NS_ENSURE_SUCCESS(rv, rv); + + nsAutoString bidi; + rv = aAlert->GetDir(bidi); + NS_ENSURE_SUCCESS(rv, rv); + + nsAutoString lang; + rv = aAlert->GetLang(lang); + NS_ENSURE_SUCCESS(rv, rv); + + nsAutoString source; + rv = aAlert->GetSource(source); + NS_ENSURE_SUCCESS(rv, rv); + + bool requireInteraction; + rv = aAlert->GetRequireInteraction(&requireInteraction); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIWindowWatcher> wwatch(do_GetService(NS_WINDOWWATCHER_CONTRACTID)); + + nsCOMPtr<nsIMutableArray> argsArray = nsArray::Create(); + + // create scriptable versions of our strings that we can store in our nsIMutableArray.... + nsCOMPtr<nsISupportsString> scriptableImageUrl (do_CreateInstance(NS_SUPPORTS_STRING_CONTRACTID)); + NS_ENSURE_TRUE(scriptableImageUrl, NS_ERROR_FAILURE); + + scriptableImageUrl->SetData(imageUrl); + rv = argsArray->AppendElement(scriptableImageUrl, /*weak =*/ false); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsISupportsString> scriptableAlertTitle (do_CreateInstance(NS_SUPPORTS_STRING_CONTRACTID)); + NS_ENSURE_TRUE(scriptableAlertTitle, NS_ERROR_FAILURE); + + scriptableAlertTitle->SetData(title); + rv = argsArray->AppendElement(scriptableAlertTitle, /*weak =*/ false); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsISupportsString> scriptableAlertText (do_CreateInstance(NS_SUPPORTS_STRING_CONTRACTID)); + NS_ENSURE_TRUE(scriptableAlertText, NS_ERROR_FAILURE); + + scriptableAlertText->SetData(text); + rv = argsArray->AppendElement(scriptableAlertText, /*weak =*/ false); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsISupportsPRBool> scriptableIsClickable (do_CreateInstance(NS_SUPPORTS_PRBOOL_CONTRACTID)); + NS_ENSURE_TRUE(scriptableIsClickable, NS_ERROR_FAILURE); + + scriptableIsClickable->SetData(textClickable); + rv = argsArray->AppendElement(scriptableIsClickable, /*weak =*/ false); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsISupportsString> scriptableAlertCookie (do_CreateInstance(NS_SUPPORTS_STRING_CONTRACTID)); + NS_ENSURE_TRUE(scriptableAlertCookie, NS_ERROR_FAILURE); + + scriptableAlertCookie->SetData(cookie); + rv = argsArray->AppendElement(scriptableAlertCookie, /*weak =*/ false); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsISupportsPRInt32> scriptableOrigin (do_CreateInstance(NS_SUPPORTS_PRINT32_CONTRACTID)); + NS_ENSURE_TRUE(scriptableOrigin, NS_ERROR_FAILURE); + + int32_t origin = + LookAndFeel::GetInt(LookAndFeel::eIntID_AlertNotificationOrigin); + scriptableOrigin->SetData(origin); + + rv = argsArray->AppendElement(scriptableOrigin, /*weak =*/ false); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsISupportsString> scriptableBidi (do_CreateInstance(NS_SUPPORTS_STRING_CONTRACTID)); + NS_ENSURE_TRUE(scriptableBidi, NS_ERROR_FAILURE); + + scriptableBidi->SetData(bidi); + rv = argsArray->AppendElement(scriptableBidi, /*weak =*/ false); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsISupportsString> scriptableLang (do_CreateInstance(NS_SUPPORTS_STRING_CONTRACTID)); + NS_ENSURE_TRUE(scriptableLang, NS_ERROR_FAILURE); + + scriptableLang->SetData(lang); + rv = argsArray->AppendElement(scriptableLang, /*weak =*/ false); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsISupportsPRBool> scriptableRequireInteraction (do_CreateInstance(NS_SUPPORTS_PRBOOL_CONTRACTID)); + NS_ENSURE_TRUE(scriptableRequireInteraction, NS_ERROR_FAILURE); + + scriptableRequireInteraction->SetData(requireInteraction); + rv = argsArray->AppendElement(scriptableRequireInteraction, /*weak =*/ false); + NS_ENSURE_SUCCESS(rv, rv); + + // Alerts with the same name should replace the old alert in the same position. + // Provide the new alert window with a pointer to the replaced window so that + // it may take the same position. + nsCOMPtr<nsISupportsInterfacePointer> replacedWindow = do_CreateInstance(NS_SUPPORTS_INTERFACE_POINTER_CONTRACTID, &rv); + NS_ENSURE_TRUE(replacedWindow, NS_ERROR_FAILURE); + mozIDOMWindowProxy* previousAlert = mNamedWindows.GetWeak(name); + replacedWindow->SetData(previousAlert); + replacedWindow->SetDataIID(&NS_GET_IID(mozIDOMWindowProxy)); + rv = argsArray->AppendElement(replacedWindow, /*weak =*/ false); + NS_ENSURE_SUCCESS(rv, rv); + + if (requireInteraction) { + mPersistentAlertCount++; + } + + // Add an observer (that wraps aAlertListener) to remove the window from + // mNamedWindows when it is closed. + nsCOMPtr<nsISupportsInterfacePointer> ifptr = do_CreateInstance(NS_SUPPORTS_INTERFACE_POINTER_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + RefPtr<nsXULAlertObserver> alertObserver = new nsXULAlertObserver(this, name, aAlertListener, requireInteraction); + nsCOMPtr<nsISupports> iSupports(do_QueryInterface(alertObserver)); + ifptr->SetData(iSupports); + ifptr->SetDataIID(&NS_GET_IID(nsIObserver)); + rv = argsArray->AppendElement(ifptr, /*weak =*/ false); + NS_ENSURE_SUCCESS(rv, rv); + + // The source contains the host and port of the site that sent the + // notification. It is empty for system alerts. + nsCOMPtr<nsISupportsString> scriptableAlertSource (do_CreateInstance(NS_SUPPORTS_STRING_CONTRACTID)); + NS_ENSURE_TRUE(scriptableAlertSource, NS_ERROR_FAILURE); + scriptableAlertSource->SetData(source); + rv = argsArray->AppendElement(scriptableAlertSource, /*weak =*/ false); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsISupportsCString> scriptableIconURL (do_CreateInstance(NS_SUPPORTS_CSTRING_CONTRACTID)); + NS_ENSURE_TRUE(scriptableIconURL, NS_ERROR_FAILURE); + if (aIconURI) { + nsAutoCString iconURL; + rv = aIconURI->GetSpec(iconURL); + NS_ENSURE_SUCCESS(rv, rv); + scriptableIconURL->SetData(iconURL); + } + rv = argsArray->AppendElement(scriptableIconURL, /*weak =*/ false); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<mozIDOMWindowProxy> newWindow; + nsAutoCString features("chrome,dialog=yes,titlebar=no,popup=yes"); + if (inPrivateBrowsing) { + features.AppendLiteral(",private"); + } + rv = wwatch->OpenWindow(nullptr, ALERT_CHROME_URL, "_blank", features.get(), + argsArray, getter_AddRefs(newWindow)); + NS_ENSURE_SUCCESS(rv, rv); + + mNamedWindows.Put(name, newWindow); + alertObserver->SetAlertWindow(newWindow); + + return NS_OK; +} + +NS_IMETHODIMP +nsXULAlerts::SetManualDoNotDisturb(bool aDoNotDisturb) +{ + mDoNotDisturb = aDoNotDisturb; + return NS_OK; +} + +NS_IMETHODIMP +nsXULAlerts::GetManualDoNotDisturb(bool* aRetVal) +{ + *aRetVal = mDoNotDisturb; + return NS_OK; +} + +NS_IMETHODIMP +nsXULAlerts::CloseAlert(const nsAString& aAlertName, + nsIPrincipal* aPrincipal) +{ + mozIDOMWindowProxy* alert = mNamedWindows.GetWeak(aAlertName); + if (nsCOMPtr<nsPIDOMWindowOuter> domWindow = nsPIDOMWindowOuter::From(alert)) { + domWindow->DispatchCustomEvent(NS_LITERAL_STRING("XULAlertClose")); + } + return NS_OK; +} + diff --git a/components/alerts/src/nsXULAlerts.h b/components/alerts/src/nsXULAlerts.h new file mode 100644 index 000000000..557716ee6 --- /dev/null +++ b/components/alerts/src/nsXULAlerts.h @@ -0,0 +1,84 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode:nil; c-basic-offset: 2 -*- */ +/* 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/. */ + +#ifndef nsXULAlerts_h__ +#define nsXULAlerts_h__ + +#include "nsCycleCollectionParticipant.h" +#include "nsDataHashtable.h" +#include "nsHashKeys.h" +#include "nsInterfaceHashtable.h" + +#include "mozIDOMWindow.h" +#include "nsIObserver.h" + +struct PendingAlert +{ + void Init(nsIAlertNotification* aAlert, nsIObserver* aListener) + { + mAlert = aAlert; + mListener = aListener; + } + nsCOMPtr<nsIAlertNotification> mAlert; + nsCOMPtr<nsIObserver> mListener; +}; + +class nsXULAlerts : public nsIAlertsService, + public nsIAlertsDoNotDisturb, + public nsIAlertsIconURI +{ + friend class nsXULAlertObserver; +public: + NS_DECL_NSIALERTSICONURI + NS_DECL_NSIALERTSDONOTDISTURB + NS_DECL_NSIALERTSSERVICE + NS_DECL_ISUPPORTS + + nsXULAlerts() + { + } + + static already_AddRefed<nsXULAlerts> GetInstance(); + +protected: + virtual ~nsXULAlerts() {} + void PersistentAlertFinished(); + + nsInterfaceHashtable<nsStringHashKey, mozIDOMWindowProxy> mNamedWindows; + uint32_t mPersistentAlertCount = 0; + nsTArray<PendingAlert> mPendingPersistentAlerts; + bool mDoNotDisturb = false; +}; + +/** + * This class wraps observers for alerts and watches + * for the "alertfinished" event in order to release + * the reference on the nsIDOMWindow of the XUL alert. + */ +class nsXULAlertObserver : public nsIObserver { +public: + NS_DECL_CYCLE_COLLECTING_ISUPPORTS + NS_DECL_NSIOBSERVER + NS_DECL_CYCLE_COLLECTION_CLASS(nsXULAlertObserver) + + nsXULAlertObserver(nsXULAlerts* aXULAlerts, const nsAString& aAlertName, + nsIObserver* aObserver, bool aIsPersistent) + : mXULAlerts(aXULAlerts), mAlertName(aAlertName), + mObserver(aObserver), mIsPersistent(aIsPersistent) {} + + void SetAlertWindow(mozIDOMWindowProxy* aWindow) { mAlertWindow = aWindow; } + +protected: + virtual ~nsXULAlertObserver() {} + + RefPtr<nsXULAlerts> mXULAlerts; + nsString mAlertName; + nsCOMPtr<mozIDOMWindowProxy> mAlertWindow; + nsCOMPtr<nsIObserver> mObserver; + bool mIsPersistent; +}; + +#endif /* nsXULAlerts_h__ */ + |