diff options
Diffstat (limited to 'toolkit/components/alerts/nsXULAlerts.cpp')
-rw-r--r-- | toolkit/components/alerts/nsXULAlerts.cpp | 398 |
1 files changed, 398 insertions, 0 deletions
diff --git a/toolkit/components/alerts/nsXULAlerts.cpp b/toolkit/components/alerts/nsXULAlerts.cpp new file mode 100644 index 0000000000..882617637a --- /dev/null +++ b/toolkit/components/alerts/nsXULAlerts.cpp @@ -0,0 +1,398 @@ +/* -*- 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; +using mozilla::dom::NotificationTelemetryService; + +#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() +{ + // Gecko on Android does not fully support XUL windows. +#ifndef MOZ_WIDGET_ANDROID + if (!gXULAlerts) { + gXULAlerts = new nsXULAlerts(); + ClearOnShutdown(&gXULAlerts); + } +#endif // MOZ_WIDGET_ANDROID + 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 (!inPrivateBrowsing) { + RefPtr<NotificationTelemetryService> telemetry = + NotificationTelemetryService::GetInstance(); + if (telemetry) { + // Record the number of unique senders for XUL alerts. The OS X and + // libnotify backends will fire `alertshow` even if "do not disturb" + // is enabled. In that case, `NotificationObserver` will record the + // sender. + nsCOMPtr<nsIPrincipal> principal; + if (NS_SUCCEEDED(aAlert->GetPrincipal(getter_AddRefs(principal)))) { + Unused << NS_WARN_IF(NS_FAILED(telemetry->RecordSender(principal))); + } + } + } + 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; +} + |