mirror of
https://repo.dactyloidae.xyz/Dactyloidae/UXP.git
synced 2026-10-24 08:07:30 +09:00
import FIREFOX_52_6_0esr_RELEASE from mozilla-esr52 hg repo
This commit is contained in:
commit
dcd9973243
150858 changed files with 23884658 additions and 0 deletions
361
toolkit/components/alerts/AlertNotification.cpp
Normal file
361
toolkit/components/alerts/AlertNotification.cpp
Normal file
|
|
@ -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
|
||||
81
toolkit/components/alerts/AlertNotification.h
Normal file
81
toolkit/components/alerts/AlertNotification.h
Normal file
|
|
@ -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__ */
|
||||
122
toolkit/components/alerts/AlertNotificationIPCSerializer.h
Normal file
122
toolkit/components/alerts/AlertNotificationIPCSerializer.h
Normal file
|
|
@ -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__ */
|
||||
8
toolkit/components/alerts/jar.mn
Normal file
8
toolkit/components/alerts/jar.mn
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
# 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/.
|
||||
|
||||
toolkit.jar:
|
||||
content/global/alerts/alert.css (resources/content/alert.css)
|
||||
content/global/alerts/alert.xul (resources/content/alert.xul)
|
||||
content/global/alerts/alert.js (resources/content/alert.js)
|
||||
38
toolkit/components/alerts/moz.build
Normal file
38
toolkit/components/alerts/moz.build
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
# -*- Mode: python; 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/.
|
||||
|
||||
MOCHITEST_MANIFESTS += ['test/mochitest.ini']
|
||||
|
||||
XPIDL_SOURCES += [
|
||||
'nsIAlertsService.idl',
|
||||
]
|
||||
|
||||
XPIDL_MODULE = 'alerts'
|
||||
|
||||
EXPORTS += [
|
||||
'nsAlertsUtils.h',
|
||||
]
|
||||
|
||||
EXPORTS.mozilla += [
|
||||
'AlertNotification.h',
|
||||
'AlertNotificationIPCSerializer.h',
|
||||
]
|
||||
|
||||
UNIFIED_SOURCES += [
|
||||
'AlertNotification.cpp',
|
||||
'nsAlertsService.cpp',
|
||||
'nsAlertsUtils.cpp',
|
||||
'nsXULAlerts.cpp',
|
||||
]
|
||||
|
||||
include('/ipc/chromium/chromium-config.mozbuild')
|
||||
|
||||
FINAL_LIBRARY = 'xul'
|
||||
|
||||
JAR_MANIFESTS += ['jar.mn']
|
||||
|
||||
with Files('**'):
|
||||
BUG_COMPONENT = ('Toolkit', 'Notifications and Alerts')
|
||||
320
toolkit/components/alerts/nsAlertsService.cpp
Normal file
320
toolkit/components/alerts/nsAlertsService.cpp
Normal file
|
|
@ -0,0 +1,320 @@
|
|||
/* -*- 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 "mozilla/Telemetry.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
|
||||
|
||||
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
|
||||
HMODULE shellDLL = ::LoadLibraryW(L"shell32.dll");
|
||||
if (!shellDLL)
|
||||
return result;
|
||||
|
||||
SHQueryUserNotificationStatePtr pSHQueryUserNotificationState =
|
||||
(SHQueryUserNotificationStatePtr) ::GetProcAddress(shellDLL, "SHQueryUserNotificationState");
|
||||
|
||||
if (pSHQueryUserNotificationState) {
|
||||
MOZ_QUERY_USER_NOTIFICATION_STATE qstate;
|
||||
if (SUCCEEDED(pSHQueryUserNotificationState(&qstate))) {
|
||||
if (qstate != QUNS_ACCEPTS_NOTIFICATIONS) {
|
||||
result = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
::FreeLibrary(shellDLL);
|
||||
#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)
|
||||
{
|
||||
#ifdef MOZ_WIDGET_ANDROID
|
||||
return NS_ERROR_NOT_IMPLEMENTED;
|
||||
#else
|
||||
nsCOMPtr<nsIAlertsDoNotDisturb> alertsDND(GetDNDBackend());
|
||||
NS_ENSURE_TRUE(alertsDND, NS_ERROR_NOT_IMPLEMENTED);
|
||||
return alertsDND->GetManualDoNotDisturb(aRetVal);
|
||||
#endif
|
||||
}
|
||||
|
||||
NS_IMETHODIMP nsAlertsService::SetManualDoNotDisturb(bool aDoNotDisturb)
|
||||
{
|
||||
#ifdef MOZ_WIDGET_ANDROID
|
||||
return NS_ERROR_NOT_IMPLEMENTED;
|
||||
#else
|
||||
nsCOMPtr<nsIAlertsDoNotDisturb> alertsDND(GetDNDBackend());
|
||||
NS_ENSURE_TRUE(alertsDND, NS_ERROR_NOT_IMPLEMENTED);
|
||||
|
||||
nsresult rv = alertsDND->SetManualDoNotDisturb(aDoNotDisturb);
|
||||
if (NS_SUCCEEDED(rv)) {
|
||||
Telemetry::Accumulate(Telemetry::ALERTS_SERVICE_DND_ENABLED, 1);
|
||||
}
|
||||
return rv;
|
||||
#endif
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
48
toolkit/components/alerts/nsAlertsService.h
Normal file
48
toolkit/components/alerts/nsAlertsService.h
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
// /* -*- 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"
|
||||
|
||||
#ifdef XP_WIN
|
||||
typedef enum tagMOZ_QUERY_USER_NOTIFICATION_STATE {
|
||||
QUNS_NOT_PRESENT = 1,
|
||||
QUNS_BUSY = 2,
|
||||
QUNS_RUNNING_D3D_FULL_SCREEN = 3,
|
||||
QUNS_PRESENTATION_MODE = 4,
|
||||
QUNS_ACCEPTS_NOTIFICATIONS = 5,
|
||||
QUNS_QUIET_TIME = 6,
|
||||
QUNS_IMMERSIVE = 7
|
||||
} MOZ_QUERY_USER_NOTIFICATION_STATE;
|
||||
|
||||
extern "C" {
|
||||
// This function is Windows Vista or later
|
||||
typedef HRESULT (__stdcall *SHQueryUserNotificationStatePtr)(MOZ_QUERY_USER_NOTIFICATION_STATE *pquns);
|
||||
}
|
||||
#endif // defined(XP_WIN)
|
||||
|
||||
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__ */
|
||||
43
toolkit/components/alerts/nsAlertsUtils.cpp
Normal file
43
toolkit/components/alerts/nsAlertsUtils.cpp
Normal file
|
|
@ -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);
|
||||
}
|
||||
32
toolkit/components/alerts/nsAlertsUtils.h
Normal file
32
toolkit/components/alerts/nsAlertsUtils.h
Normal file
|
|
@ -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 */
|
||||
259
toolkit/components/alerts/nsIAlertsService.idl
Normal file
259
toolkit/components/alerts/nsIAlertsService.idl
Normal file
|
|
@ -0,0 +1,259 @@
|
|||
/* -*- Mode: IDL; 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 "nsISupports.idl"
|
||||
#include "nsIObserver.idl"
|
||||
|
||||
interface imgIRequest;
|
||||
interface nsICancelable;
|
||||
interface nsIPrincipal;
|
||||
interface nsIURI;
|
||||
|
||||
%{C++
|
||||
#define ALERT_NOTIFICATION_CONTRACTID "@mozilla.org/alert-notification;1"
|
||||
%}
|
||||
|
||||
[scriptable, uuid(a71a637d-de1d-47c6-a8d2-c60b2596f471)]
|
||||
interface nsIAlertNotificationImageListener : nsISupports
|
||||
{
|
||||
/**
|
||||
* Called when the image finishes loading.
|
||||
*
|
||||
* @param aUserData An opaque parameter passed to |loadImage|.
|
||||
* @param aRequest The image request.
|
||||
*/
|
||||
void onImageReady(in nsISupports aUserData, in imgIRequest aRequest);
|
||||
|
||||
/**
|
||||
* Called if the alert doesn't have an image, or if the image request times
|
||||
* out or fails.
|
||||
*
|
||||
* @param aUserData An opaque parameter passed to |loadImage|.
|
||||
*/
|
||||
void onImageMissing(in nsISupports aUserData);
|
||||
};
|
||||
|
||||
[scriptable, uuid(cf2e4cb6-4b8f-4eca-aea9-d51a8f9f7a50)]
|
||||
interface nsIAlertNotification : nsISupports
|
||||
{
|
||||
/** Initializes an alert notification. */
|
||||
void init([optional] in AString aName,
|
||||
[optional] in AString aImageURL,
|
||||
[optional] in AString aTitle,
|
||||
[optional] in AString aText,
|
||||
[optional] in boolean aTextClickable,
|
||||
[optional] in AString aCookie,
|
||||
[optional] in AString aDir,
|
||||
[optional] in AString aLang,
|
||||
[optional] in AString aData,
|
||||
[optional] in nsIPrincipal aPrincipal,
|
||||
[optional] in boolean aInPrivateBrowsing,
|
||||
[optional] in boolean aRequireInteraction);
|
||||
|
||||
/**
|
||||
* The name of the notification. On Android, the name is hashed and used as
|
||||
* a notification ID. Notifications will replace previous notifications with
|
||||
* the same name.
|
||||
*/
|
||||
readonly attribute AString name;
|
||||
|
||||
/**
|
||||
* A URL identifying the image to put in the alert. The OS X backend limits
|
||||
* the amount of time it will wait for the image to load to six seconds. After
|
||||
* that time, the alert will show without an image.
|
||||
*/
|
||||
readonly attribute AString imageURL;
|
||||
|
||||
/** The title for the alert. */
|
||||
readonly attribute AString title;
|
||||
|
||||
/** The contents of the alert. */
|
||||
readonly attribute AString text;
|
||||
|
||||
/**
|
||||
* Controls the click behavior. If true, the alert listener will be notified
|
||||
* when the user clicks on the alert.
|
||||
*/
|
||||
readonly attribute boolean textClickable;
|
||||
|
||||
/**
|
||||
* An opaque cookie that will be passed to the alert listener for each
|
||||
* callback.
|
||||
*/
|
||||
readonly attribute AString cookie;
|
||||
|
||||
/**
|
||||
* Bidi override for the title and contents. Valid values are "auto", "ltr",
|
||||
* or "rtl". Ignored if the backend doesn't support localization.
|
||||
*/
|
||||
readonly attribute AString dir;
|
||||
|
||||
/**
|
||||
* Language of the title and text. Ignored if the backend doesn't support
|
||||
* localization.
|
||||
*/
|
||||
readonly attribute AString lang;
|
||||
|
||||
/**
|
||||
* A Base64-encoded structured clone buffer containing data associated with
|
||||
* this alert. Only used for web notifications. Chrome callers should use a
|
||||
* cookie instead.
|
||||
*/
|
||||
readonly attribute AString data;
|
||||
|
||||
/**
|
||||
* The principal of the page that created the alert. Used for IPC security
|
||||
* checks, and to determine whether the alert is actionable.
|
||||
*/
|
||||
readonly attribute nsIPrincipal principal;
|
||||
|
||||
/**
|
||||
* The URI of the page that created the alert. |null| if the alert is not
|
||||
* actionable.
|
||||
*/
|
||||
readonly attribute nsIURI URI;
|
||||
|
||||
/**
|
||||
* Controls the image loading behavior. If true, the image request will be
|
||||
* loaded anonymously (without cookies or authorization tokens).
|
||||
*/
|
||||
readonly attribute boolean inPrivateBrowsing;
|
||||
|
||||
/**
|
||||
* Indicates that the notification should remain readily available until
|
||||
* the user activates or dismisses the notification.
|
||||
*/
|
||||
readonly attribute boolean requireInteraction;
|
||||
|
||||
/**
|
||||
* Indicates whether this alert should show the source string and action
|
||||
* buttons. False for system alerts (which can omit the principal), or
|
||||
* expanded, system, and null principals.
|
||||
*/
|
||||
readonly attribute boolean actionable;
|
||||
|
||||
/**
|
||||
* The host and port of the originating page, or an empty string if the alert
|
||||
* is not actionable.
|
||||
*/
|
||||
readonly attribute AString source;
|
||||
|
||||
/**
|
||||
* Loads the image associated with this alert.
|
||||
*
|
||||
* @param aTimeout The number of milliseconds to wait before cancelling the
|
||||
* image request. If zero, there is no timeout.
|
||||
* @param aListener An |nsIAlertNotificationImageListener| implementation,
|
||||
* notified when the image loads. The listener is kept alive
|
||||
* until the request completes.
|
||||
* @param aUserData An opaque parameter passed to the listener's methods.
|
||||
* Not used by the libnotify backend, but the OS X backend
|
||||
* passes the pending notification.
|
||||
*/
|
||||
nsICancelable loadImage(in unsigned long aTimeout,
|
||||
in nsIAlertNotificationImageListener aListener,
|
||||
[optional] in nsISupports aUserData);
|
||||
};
|
||||
|
||||
[scriptable, uuid(f7a36392-d98b-4141-a7d7-4e46642684e3)]
|
||||
interface nsIAlertsService : nsISupports
|
||||
{
|
||||
void showPersistentNotification(in AString aPersistentData,
|
||||
in nsIAlertNotification aAlert,
|
||||
[optional] in nsIObserver aAlertListener);
|
||||
|
||||
void showAlert(in nsIAlertNotification aAlert,
|
||||
[optional] in nsIObserver aAlertListener);
|
||||
/**
|
||||
* Initializes and shows an |nsIAlertNotification| with the given parameters.
|
||||
*
|
||||
* @param aAlertListener Used for callbacks. May be null if the caller
|
||||
* doesn't care about callbacks.
|
||||
* @see nsIAlertNotification for descriptions of all other parameters.
|
||||
* @throws NS_ERROR_NOT_AVAILABLE If the notification cannot be displayed.
|
||||
*
|
||||
* The following arguments will be passed to the alertListener's observe()
|
||||
* method:
|
||||
* subject - null
|
||||
* topic - "alertfinished" when the alert goes away
|
||||
* "alertdisablecallback" when alerts should be disabled for the principal
|
||||
* "alertsettingscallback" when alert settings should be opened
|
||||
* "alertclickcallback" when the text is clicked
|
||||
* "alertshow" when the alert is shown
|
||||
* data - the value of the cookie parameter passed to showAlertNotification.
|
||||
*
|
||||
* @note Depending on current circumstances (if the user's in a fullscreen
|
||||
* application, for instance), the alert might not be displayed at all.
|
||||
* In that case, if an alert listener is passed in it will receive the
|
||||
* "alertfinished" notification immediately.
|
||||
*/
|
||||
void showAlertNotification(in AString aImageURL,
|
||||
in AString aTitle,
|
||||
in AString aText,
|
||||
[optional] in boolean aTextClickable,
|
||||
[optional] in AString aCookie,
|
||||
[optional] in nsIObserver aAlertListener,
|
||||
[optional] in AString aName,
|
||||
[optional] in AString aDir,
|
||||
[optional] in AString aLang,
|
||||
[optional] in AString aData,
|
||||
[optional] in nsIPrincipal aPrincipal,
|
||||
[optional] in boolean aInPrivateBrowsing,
|
||||
[optional] in boolean aRequireInteraction);
|
||||
|
||||
/**
|
||||
* Close alerts created by the service.
|
||||
*
|
||||
* @param aName The name of the notification to close. If no name
|
||||
* is provided then only a notification created with
|
||||
* no name (if any) will be closed.
|
||||
*/
|
||||
void closeAlert([optional] in AString aName,
|
||||
[optional] in nsIPrincipal aPrincipal);
|
||||
|
||||
};
|
||||
|
||||
[scriptable, uuid(c5d63e3a-259d-45a8-b964-8377967cb4d2)]
|
||||
interface nsIAlertsDoNotDisturb : nsISupports
|
||||
{
|
||||
/**
|
||||
* Toggles a manual Do Not Disturb mode for the service to reduce the amount
|
||||
* of disruption that alerts cause the user.
|
||||
* This may mean only displaying them in a notification tray/center or not
|
||||
* displaying them at all. If a system backend already supports a similar
|
||||
* feature controlled by the user, enabling this may not have any impact on
|
||||
* code to show an alert. e.g. on OS X, the system will take care not
|
||||
* disrupting a user if we simply create a notification like usual.
|
||||
*/
|
||||
attribute bool manualDoNotDisturb;
|
||||
};
|
||||
|
||||
[scriptable, uuid(fc6d7f0a-0cf6-4268-8c71-ab640842b9b1)]
|
||||
interface nsIAlertsIconData : nsISupports
|
||||
{
|
||||
/**
|
||||
* Shows an alert with an icon. Web notifications use the favicon of the
|
||||
* page that created the alert. If the favicon is not in the Places database,
|
||||
* |aIconSize| will be zero.
|
||||
*/
|
||||
void showAlertWithIconData(in nsIAlertNotification aAlert,
|
||||
[optional] in nsIObserver aAlertListener,
|
||||
[optional] in uint32_t aIconSize,
|
||||
[const, array, size_is(aIconSize)] in uint8_t
|
||||
aIconData);
|
||||
};
|
||||
|
||||
[scriptable, uuid(f3c82915-bf60-41ea-91ce-6c46b22e381a)]
|
||||
interface nsIAlertsIconURI : nsISupports
|
||||
{
|
||||
/**
|
||||
* Shows an alert with an icon URI. Web notifications use |moz-anno:|
|
||||
* URIs to reference favicons from Places. If the page doesn't have a
|
||||
* favicon, |aIconURI| will be |null|.
|
||||
*/
|
||||
void showAlertWithIconURI(in nsIAlertNotification aAlert,
|
||||
[optional] in nsIObserver aAlertListener,
|
||||
[optional] in nsIURI aIconURI);
|
||||
};
|
||||
398
toolkit/components/alerts/nsXULAlerts.cpp
Normal file
398
toolkit/components/alerts/nsXULAlerts.cpp
Normal file
|
|
@ -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;
|
||||
}
|
||||
|
||||
84
toolkit/components/alerts/nsXULAlerts.h
Normal file
84
toolkit/components/alerts/nsXULAlerts.h
Normal file
|
|
@ -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__ */
|
||||
|
||||
34
toolkit/components/alerts/resources/content/alert.css
Normal file
34
toolkit/components/alerts/resources/content/alert.css
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
/* 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/. */
|
||||
|
||||
#alertBox[animate] {
|
||||
animation-duration: 20s;
|
||||
animation-fill-mode: both;
|
||||
animation-name: alert-animation;
|
||||
}
|
||||
|
||||
#alertBox[animate]:not([clicked]):not([closing]):hover {
|
||||
animation-play-state: paused;
|
||||
}
|
||||
|
||||
#alertBox:not([hasOrigin]) > box > #alertTextBox > #alertFooter,
|
||||
#alertBox:not([hasIcon]) > box > #alertIcon,
|
||||
#alertImage:not([src]) {
|
||||
display: none;
|
||||
}
|
||||
|
||||
#alertTitleBox {
|
||||
-moz-box-pack: center;
|
||||
-moz-box-align: center;
|
||||
}
|
||||
|
||||
.alertText {
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
|
||||
@keyframes alert-animation {
|
||||
to {
|
||||
visibility: hidden;
|
||||
}
|
||||
}
|
||||
332
toolkit/components/alerts/resources/content/alert.js
Normal file
332
toolkit/components/alerts/resources/content/alert.js
Normal file
|
|
@ -0,0 +1,332 @@
|
|||
/* 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/. */
|
||||
|
||||
var {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
|
||||
|
||||
Cu.import("resource://gre/modules/AppConstants.jsm");
|
||||
Cu.import("resource://gre/modules/Services.jsm");
|
||||
|
||||
// Copied from nsILookAndFeel.h, see comments on eMetric_AlertNotificationOrigin
|
||||
const NS_ALERT_HORIZONTAL = 1;
|
||||
const NS_ALERT_LEFT = 2;
|
||||
const NS_ALERT_TOP = 4;
|
||||
|
||||
const WINDOW_MARGIN = AppConstants.platform == "win" ? 0 : 10;
|
||||
const BODY_TEXT_LIMIT = 200;
|
||||
const WINDOW_SHADOW_SPREAD = AppConstants.platform == "win" ? 10 : 0;
|
||||
|
||||
|
||||
var gOrigin = 0; // Default value: alert from bottom right.
|
||||
var gReplacedWindow = null;
|
||||
var gAlertListener = null;
|
||||
var gAlertTextClickable = false;
|
||||
var gAlertCookie = "";
|
||||
var gIsReplaced = false;
|
||||
var gRequireInteraction = false;
|
||||
|
||||
function prefillAlertInfo() {
|
||||
// unwrap all the args....
|
||||
// arguments[0] --> the image src url
|
||||
// arguments[1] --> the alert title
|
||||
// arguments[2] --> the alert text
|
||||
// arguments[3] --> is the text clickable?
|
||||
// arguments[4] --> the alert cookie to be passed back to the listener
|
||||
// arguments[5] --> the alert origin reported by the look and feel
|
||||
// arguments[6] --> bidi
|
||||
// arguments[7] --> lang
|
||||
// arguments[8] --> requires interaction
|
||||
// arguments[9] --> replaced alert window (nsIDOMWindow)
|
||||
// arguments[10] --> an optional callback listener (nsIObserver)
|
||||
// arguments[11] -> the nsIURI.hostPort of the origin, optional
|
||||
// arguments[12] -> the alert icon URL, optional
|
||||
|
||||
switch (window.arguments.length) {
|
||||
default:
|
||||
case 13: {
|
||||
if (window.arguments[12]) {
|
||||
let alertBox = document.getElementById("alertBox");
|
||||
alertBox.setAttribute("hasIcon", true);
|
||||
|
||||
let icon = document.getElementById("alertIcon");
|
||||
icon.src = window.arguments[12];
|
||||
}
|
||||
}
|
||||
case 12: {
|
||||
if (window.arguments[11]) {
|
||||
let alertBox = document.getElementById("alertBox");
|
||||
alertBox.setAttribute("hasOrigin", true);
|
||||
|
||||
let hostPort = window.arguments[11];
|
||||
const ALERT_BUNDLE = Services.strings.createBundle(
|
||||
"chrome://alerts/locale/alert.properties");
|
||||
const BRAND_BUNDLE = Services.strings.createBundle(
|
||||
"chrome://branding/locale/brand.properties");
|
||||
const BRAND_NAME = BRAND_BUNDLE.GetStringFromName("brandShortName");
|
||||
let label = document.getElementById("alertSourceLabel");
|
||||
label.setAttribute("value",
|
||||
ALERT_BUNDLE.formatStringFromName("source.label",
|
||||
[hostPort],
|
||||
1));
|
||||
let doNotDisturbMenuItem = document.getElementById("doNotDisturbMenuItem");
|
||||
doNotDisturbMenuItem.setAttribute("label",
|
||||
ALERT_BUNDLE.formatStringFromName("doNotDisturb.label",
|
||||
[BRAND_NAME],
|
||||
1));
|
||||
let disableForOrigin = document.getElementById("disableForOriginMenuItem");
|
||||
disableForOrigin.setAttribute("label",
|
||||
ALERT_BUNDLE.formatStringFromName("webActions.disableForOrigin.label",
|
||||
[hostPort],
|
||||
1));
|
||||
let openSettings = document.getElementById("openSettingsMenuItem");
|
||||
openSettings.setAttribute("label",
|
||||
ALERT_BUNDLE.GetStringFromName("webActions.settings.label"));
|
||||
}
|
||||
}
|
||||
case 11:
|
||||
gAlertListener = window.arguments[10];
|
||||
case 10:
|
||||
gReplacedWindow = window.arguments[9];
|
||||
case 9:
|
||||
gRequireInteraction = window.arguments[8];
|
||||
case 8:
|
||||
if (window.arguments[7]) {
|
||||
document.getElementById("alertTitleLabel").setAttribute("lang", window.arguments[7]);
|
||||
document.getElementById("alertTextLabel").setAttribute("lang", window.arguments[7]);
|
||||
}
|
||||
case 7:
|
||||
if (window.arguments[6]) {
|
||||
document.getElementById("alertNotification").style.direction = window.arguments[6];
|
||||
}
|
||||
case 6:
|
||||
gOrigin = window.arguments[5];
|
||||
case 5:
|
||||
gAlertCookie = window.arguments[4];
|
||||
case 4:
|
||||
gAlertTextClickable = window.arguments[3];
|
||||
if (gAlertTextClickable) {
|
||||
document.getElementById("alertNotification").setAttribute("clickable", true);
|
||||
document.getElementById("alertTextLabel").setAttribute("clickable", true);
|
||||
}
|
||||
case 3:
|
||||
if (window.arguments[2]) {
|
||||
document.getElementById("alertBox").setAttribute("hasBodyText", true);
|
||||
let bodyText = window.arguments[2];
|
||||
let bodyTextLabel = document.getElementById("alertTextLabel");
|
||||
|
||||
if (bodyText.length > BODY_TEXT_LIMIT) {
|
||||
bodyTextLabel.setAttribute("tooltiptext", bodyText);
|
||||
|
||||
let ellipsis = "\u2026";
|
||||
try {
|
||||
ellipsis = Services.prefs.getComplexValue("intl.ellipsis",
|
||||
Ci.nsIPrefLocalizedString).data;
|
||||
} catch (e) { }
|
||||
|
||||
// Copied from nsContextMenu.js' formatSearchContextItem().
|
||||
// If the JS character after our truncation point is a trail surrogate,
|
||||
// include it in the truncated string to avoid splitting a surrogate pair.
|
||||
let truncLength = BODY_TEXT_LIMIT;
|
||||
let truncChar = bodyText[BODY_TEXT_LIMIT].charCodeAt(0);
|
||||
if (truncChar >= 0xDC00 && truncChar <= 0xDFFF) {
|
||||
truncLength++;
|
||||
}
|
||||
|
||||
bodyText = bodyText.substring(0, truncLength) +
|
||||
ellipsis;
|
||||
}
|
||||
bodyTextLabel.textContent = bodyText;
|
||||
}
|
||||
case 2:
|
||||
document.getElementById("alertTitleLabel").setAttribute("value", window.arguments[1]);
|
||||
case 1:
|
||||
if (window.arguments[0]) {
|
||||
document.getElementById("alertBox").setAttribute("hasImage", true);
|
||||
document.getElementById("alertImage").setAttribute("src", window.arguments[0]);
|
||||
}
|
||||
case 0:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
function onAlertLoad() {
|
||||
const ALERT_DURATION_IMMEDIATE = 20000;
|
||||
let alertTextBox = document.getElementById("alertTextBox");
|
||||
let alertImageBox = document.getElementById("alertImageBox");
|
||||
alertImageBox.style.minHeight = alertTextBox.scrollHeight + "px";
|
||||
|
||||
sizeToContent();
|
||||
|
||||
if (gReplacedWindow && !gReplacedWindow.closed) {
|
||||
moveWindowToReplace(gReplacedWindow);
|
||||
gReplacedWindow.gIsReplaced = true;
|
||||
gReplacedWindow.close();
|
||||
} else {
|
||||
moveWindowToEnd();
|
||||
}
|
||||
|
||||
window.addEventListener("XULAlertClose", function() { window.close(); });
|
||||
|
||||
// If the require interaction flag is set, prevent auto-closing the notification.
|
||||
if (!gRequireInteraction) {
|
||||
if (Services.prefs.getBoolPref("alerts.disableSlidingEffect")) {
|
||||
setTimeout(function() { window.close(); }, ALERT_DURATION_IMMEDIATE);
|
||||
} else {
|
||||
let alertBox = document.getElementById("alertBox");
|
||||
alertBox.addEventListener("animationend", function hideAlert(event) {
|
||||
if (event.animationName == "alert-animation" ||
|
||||
event.animationName == "alert-clicked-animation" ||
|
||||
event.animationName == "alert-closing-animation") {
|
||||
alertBox.removeEventListener("animationend", hideAlert, false);
|
||||
window.close();
|
||||
}
|
||||
}, false);
|
||||
alertBox.setAttribute("animate", true);
|
||||
}
|
||||
}
|
||||
|
||||
let alertSettings = document.getElementById("alertSettings");
|
||||
alertSettings.addEventListener("focus", onAlertSettingsFocus);
|
||||
alertSettings.addEventListener("click", onAlertSettingsClick);
|
||||
|
||||
let ev = new CustomEvent("AlertActive", {bubbles: true, cancelable: true});
|
||||
document.documentElement.dispatchEvent(ev);
|
||||
|
||||
if (gAlertListener) {
|
||||
gAlertListener.observe(null, "alertshow", gAlertCookie);
|
||||
}
|
||||
}
|
||||
|
||||
function moveWindowToReplace(aReplacedAlert) {
|
||||
let heightDelta = window.outerHeight - aReplacedAlert.outerHeight;
|
||||
|
||||
// Move windows that come after the replaced alert if the height is different.
|
||||
if (heightDelta != 0) {
|
||||
let windows = Services.wm.getEnumerator("alert:alert");
|
||||
while (windows.hasMoreElements()) {
|
||||
let alertWindow = windows.getNext();
|
||||
// boolean to determine if the alert window is after the replaced alert.
|
||||
let alertIsAfter = gOrigin & NS_ALERT_TOP ?
|
||||
alertWindow.screenY > aReplacedAlert.screenY :
|
||||
aReplacedAlert.screenY > alertWindow.screenY;
|
||||
if (alertIsAfter) {
|
||||
// The new Y position of the window.
|
||||
let adjustedY = gOrigin & NS_ALERT_TOP ?
|
||||
alertWindow.screenY + heightDelta :
|
||||
alertWindow.screenY - heightDelta;
|
||||
alertWindow.moveTo(alertWindow.screenX, adjustedY);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let adjustedY = gOrigin & NS_ALERT_TOP ? aReplacedAlert.screenY :
|
||||
aReplacedAlert.screenY - heightDelta;
|
||||
window.moveTo(aReplacedAlert.screenX, adjustedY);
|
||||
}
|
||||
|
||||
function moveWindowToEnd() {
|
||||
// Determine position
|
||||
let x = gOrigin & NS_ALERT_LEFT ? screen.availLeft :
|
||||
screen.availLeft + screen.availWidth - window.outerWidth;
|
||||
let y = gOrigin & NS_ALERT_TOP ? screen.availTop :
|
||||
screen.availTop + screen.availHeight - window.outerHeight;
|
||||
|
||||
// Position the window at the end of all alerts.
|
||||
let windows = Services.wm.getEnumerator("alert:alert");
|
||||
while (windows.hasMoreElements()) {
|
||||
let alertWindow = windows.getNext();
|
||||
if (alertWindow != window) {
|
||||
if (gOrigin & NS_ALERT_TOP) {
|
||||
y = Math.max(y, alertWindow.screenY + alertWindow.outerHeight - WINDOW_SHADOW_SPREAD);
|
||||
} else {
|
||||
y = Math.min(y, alertWindow.screenY - window.outerHeight + WINDOW_SHADOW_SPREAD);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Offset the alert by WINDOW_MARGIN pixels from the edge of the screen
|
||||
y += gOrigin & NS_ALERT_TOP ? WINDOW_MARGIN : -WINDOW_MARGIN;
|
||||
x += gOrigin & NS_ALERT_LEFT ? WINDOW_MARGIN : -WINDOW_MARGIN;
|
||||
|
||||
window.moveTo(x, y);
|
||||
}
|
||||
|
||||
function onAlertBeforeUnload() {
|
||||
if (!gIsReplaced) {
|
||||
// Move other alert windows to fill the gap left by closing alert.
|
||||
let heightDelta = window.outerHeight + WINDOW_MARGIN - WINDOW_SHADOW_SPREAD;
|
||||
let windows = Services.wm.getEnumerator("alert:alert");
|
||||
while (windows.hasMoreElements()) {
|
||||
let alertWindow = windows.getNext();
|
||||
if (alertWindow != window) {
|
||||
if (gOrigin & NS_ALERT_TOP) {
|
||||
if (alertWindow.screenY > window.screenY) {
|
||||
alertWindow.moveTo(alertWindow.screenX, alertWindow.screenY - heightDelta);
|
||||
}
|
||||
} else if (window.screenY > alertWindow.screenY) {
|
||||
alertWindow.moveTo(alertWindow.screenX, alertWindow.screenY + heightDelta);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (gAlertListener) {
|
||||
gAlertListener.observe(null, "alertfinished", gAlertCookie);
|
||||
}
|
||||
}
|
||||
|
||||
function onAlertClick() {
|
||||
if (gAlertListener && gAlertTextClickable) {
|
||||
gAlertListener.observe(null, "alertclickcallback", gAlertCookie);
|
||||
}
|
||||
|
||||
let alertBox = document.getElementById("alertBox");
|
||||
if (alertBox.getAttribute("animate") == "true") {
|
||||
// Closed when the animation ends.
|
||||
alertBox.setAttribute("clicked", "true");
|
||||
} else {
|
||||
window.close();
|
||||
}
|
||||
}
|
||||
|
||||
function doNotDisturb() {
|
||||
const alertService = Cc["@mozilla.org/alerts-service;1"]
|
||||
.getService(Ci.nsIAlertsService)
|
||||
.QueryInterface(Ci.nsIAlertsDoNotDisturb);
|
||||
alertService.manualDoNotDisturb = true;
|
||||
Services.telemetry.getHistogramById("WEB_NOTIFICATION_MENU")
|
||||
.add(0);
|
||||
onAlertClose();
|
||||
}
|
||||
|
||||
function disableForOrigin() {
|
||||
gAlertListener.observe(null, "alertdisablecallback", gAlertCookie);
|
||||
onAlertClose();
|
||||
}
|
||||
|
||||
function onAlertSettingsFocus(event) {
|
||||
event.target.removeAttribute("focusedViaMouse");
|
||||
}
|
||||
|
||||
function onAlertSettingsClick(event) {
|
||||
// XXXjaws Hack used to remove the focus-ring only
|
||||
// from mouse interaction, but focus-ring drawing
|
||||
// should only be enabled when interacting via keyboard.
|
||||
event.target.setAttribute("focusedViaMouse", true);
|
||||
event.stopPropagation();
|
||||
}
|
||||
|
||||
function openSettings() {
|
||||
gAlertListener.observe(null, "alertsettingscallback", gAlertCookie);
|
||||
onAlertClose();
|
||||
}
|
||||
|
||||
function onAlertClose() {
|
||||
let alertBox = document.getElementById("alertBox");
|
||||
if (alertBox.getAttribute("animate") == "true") {
|
||||
// Closed when the animation ends.
|
||||
alertBox.setAttribute("closing", "true");
|
||||
} else {
|
||||
window.close();
|
||||
}
|
||||
}
|
||||
67
toolkit/components/alerts/resources/content/alert.xul
Normal file
67
toolkit/components/alerts/resources/content/alert.xul
Normal file
|
|
@ -0,0 +1,67 @@
|
|||
<?xml version="1.0"?>
|
||||
<!-- 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/. -->
|
||||
|
||||
<!DOCTYPE window [
|
||||
<!ENTITY % alertDTD SYSTEM "chrome://alerts/locale/alert.dtd">
|
||||
%alertDTD;
|
||||
]>
|
||||
|
||||
<?xml-stylesheet href="chrome://global/content/alerts/alert.css" type="text/css"?>
|
||||
<?xml-stylesheet href="chrome://global/skin/alerts/alert.css" type="text/css"?>
|
||||
|
||||
<window id="alertNotification"
|
||||
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
|
||||
windowtype="alert:alert"
|
||||
xmlns:xhtml="http://www.w3.org/1999/xhtml"
|
||||
role="alert"
|
||||
pack="start"
|
||||
onload="onAlertLoad();"
|
||||
onclick="onAlertClick();"
|
||||
onbeforeunload="onAlertBeforeUnload();">
|
||||
|
||||
<script type="application/javascript" src="chrome://global/content/alerts/alert.js"/>
|
||||
|
||||
<vbox id="alertBox" class="alertBox">
|
||||
<box id="alertTitleBox">
|
||||
<image id="alertIcon"/>
|
||||
<label id="alertTitleLabel" class="alertTitle plain" crop="end"/>
|
||||
<vbox class="alertCloseBox">
|
||||
<toolbarbutton class="alertCloseButton close-icon"
|
||||
tooltiptext="&closeAlert.tooltip;"
|
||||
onclick="event.stopPropagation();"
|
||||
oncommand="onAlertClose();"/>
|
||||
</vbox>
|
||||
</box>
|
||||
<box>
|
||||
<hbox id="alertImageBox" class="alertImageBox" align="center" pack="center">
|
||||
<image id="alertImage"/>
|
||||
</hbox>
|
||||
|
||||
<vbox id="alertTextBox" class="alertTextBox">
|
||||
<label id="alertTextLabel" class="alertText plain"/>
|
||||
<spacer flex="1"/>
|
||||
<box id="alertFooter">
|
||||
<label id="alertSourceLabel" class="alertSource plain"/>
|
||||
<button type="menu" id="alertSettings" tooltiptext="&settings.label;">
|
||||
<menupopup position="after_end">
|
||||
<menuitem id="doNotDisturbMenuItem"
|
||||
oncommand="doNotDisturb();"/>
|
||||
<menuseparator/>
|
||||
<menuitem id="disableForOriginMenuItem"
|
||||
oncommand="disableForOrigin();"/>
|
||||
<menuitem id="openSettingsMenuItem"
|
||||
oncommand="openSettings();"/>
|
||||
</menupopup>
|
||||
</button>
|
||||
</box>
|
||||
</vbox>
|
||||
</box>
|
||||
</vbox>
|
||||
|
||||
<!-- This method is called inline because we want to make sure we establish the width
|
||||
and height of the alert before we fire the onload handler. -->
|
||||
<script type="application/javascript">prefillAlertInfo();</script>
|
||||
</window>
|
||||
|
||||
7
toolkit/components/alerts/test/.eslintrc.js
Normal file
7
toolkit/components/alerts/test/.eslintrc.js
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
"use strict";
|
||||
|
||||
module.exports = {
|
||||
"extends": [
|
||||
"../../../../testing/mochitest/mochitest.eslintrc.js"
|
||||
]
|
||||
};
|
||||
BIN
toolkit/components/alerts/test/image.gif
Normal file
BIN
toolkit/components/alerts/test/image.gif
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 60 KiB |
BIN
toolkit/components/alerts/test/image.png
Normal file
BIN
toolkit/components/alerts/test/image.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 2.5 KiB |
82
toolkit/components/alerts/test/image_server.sjs
Normal file
82
toolkit/components/alerts/test/image_server.sjs
Normal file
|
|
@ -0,0 +1,82 @@
|
|||
const { classes: Cc, interfaces: Ci, utils: Cu, results: Cr, Constructor: CC } = Components;
|
||||
|
||||
Cu.import("resource://gre/modules/Timer.jsm");
|
||||
|
||||
const LocalFile = CC("@mozilla.org/file/local;1", "nsILocalFile",
|
||||
"initWithPath");
|
||||
|
||||
const FileInputStream = CC("@mozilla.org/network/file-input-stream;1",
|
||||
"nsIFileInputStream", "init");
|
||||
|
||||
const BinaryInputStream = CC("@mozilla.org/binaryinputstream;1",
|
||||
"nsIBinaryInputStream", "setInputStream");
|
||||
|
||||
function handleRequest(request, response) {
|
||||
let params = parseQueryString(request.queryString);
|
||||
|
||||
response.setStatusLine(request.httpVersion, 200, "OK");
|
||||
|
||||
// Compare and increment a cookie for this request. This is used to test
|
||||
// private browsing mode; the cookie should not be set if the image is
|
||||
// loaded anonymously.
|
||||
if (params.has("c")) {
|
||||
let expectedValue = parseInt(params.get("c"), 10);
|
||||
let actualValue = !request.hasHeader("Cookie") ? 0 :
|
||||
parseInt(request.getHeader("Cookie")
|
||||
.replace(/^counter=(\d+)/, "$1"), 10);
|
||||
if (actualValue != expectedValue) {
|
||||
response.setStatusLine(request.httpVersion, 400, "Wrong counter value");
|
||||
return;
|
||||
}
|
||||
response.setHeader("Set-Cookie", `counter=${expectedValue + 1}`, false);
|
||||
}
|
||||
|
||||
// Wait to send the image if a timeout is given.
|
||||
let timeout = parseInt(params.get("t"), 10);
|
||||
if (timeout > 0) {
|
||||
response.processAsync();
|
||||
setTimeout(() => {
|
||||
respond(params, request, response);
|
||||
response.finish();
|
||||
}, timeout * 1000);
|
||||
return;
|
||||
}
|
||||
|
||||
respond(params, request, response);
|
||||
}
|
||||
|
||||
function parseQueryString(queryString) {
|
||||
return queryString.split("&").reduce((params, param) => {
|
||||
let [key, value] = param.split("=", 2);
|
||||
params.set(key, value);
|
||||
return params;
|
||||
}, new Map());
|
||||
}
|
||||
|
||||
function respond(params, request, response) {
|
||||
if (params.has("s")) {
|
||||
let statusCode = parseInt(params.get("s"), 10);
|
||||
response.setStatusLine(request.httpVersion, statusCode, "Custom status");
|
||||
return;
|
||||
}
|
||||
var filename = params.get("f");
|
||||
writeFile(filename, response);
|
||||
}
|
||||
|
||||
function writeFile(name, response) {
|
||||
var file = new LocalFile(getState("__LOCATION__")).parent;
|
||||
file.append(name);
|
||||
|
||||
let mimeType = Cc["@mozilla.org/uriloader/external-helper-app-service;1"]
|
||||
.getService(Ci.nsIMIMEService)
|
||||
.getTypeFromFile(file);
|
||||
|
||||
let fileStream = new FileInputStream(file, 1, 0, false);
|
||||
let binaryStream = new BinaryInputStream(fileStream);
|
||||
|
||||
response.setHeader("Content-Type", mimeType, false);
|
||||
response.bodyOutputStream.writeFrom(binaryStream, binaryStream.available());
|
||||
|
||||
binaryStream.close();
|
||||
fileStream.close();
|
||||
}
|
||||
16
toolkit/components/alerts/test/mochitest.ini
Normal file
16
toolkit/components/alerts/test/mochitest.ini
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
[DEFAULT]
|
||||
support-files =
|
||||
image.gif
|
||||
image.png
|
||||
image_server.sjs
|
||||
|
||||
# Synchronous tests like test_alerts.html must come before
|
||||
# asynchronous tests like test_alerts_noobserve.html!
|
||||
[test_alerts.html]
|
||||
skip-if = toolkit == 'android'
|
||||
[test_alerts_noobserve.html]
|
||||
[test_alerts_requireinteraction.html]
|
||||
[test_image.html]
|
||||
[test_multiple_alerts.html]
|
||||
[test_principal.html]
|
||||
skip-if = toolkit == 'android'
|
||||
89
toolkit/components/alerts/test/test_alerts.html
Normal file
89
toolkit/components/alerts/test/test_alerts.html
Normal file
|
|
@ -0,0 +1,89 @@
|
|||
<!DOCTYPE HTML>
|
||||
<!-- Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ -->
|
||||
<html>
|
||||
<head>
|
||||
<title>Test for Alerts Service</title>
|
||||
<script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
|
||||
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<p id="display"></p>
|
||||
|
||||
<br>Alerts service, with observer "synchronous" case.
|
||||
<br>
|
||||
<br>Did a notification appear anywhere?
|
||||
<br>If so, the test will finish once the notification disappears.
|
||||
|
||||
<pre id="test">
|
||||
<script class="testbody" type="text/javascript">
|
||||
|
||||
var observer = {
|
||||
alertShow: false,
|
||||
observe: function (aSubject, aTopic, aData) {
|
||||
is(aData, "foobarcookie", "Checking whether the alert cookie was passed correctly");
|
||||
if (aTopic == "alertclickcallback") {
|
||||
todo(false, "Did someone click the notification while running mochitests? (Please don't.)");
|
||||
} else if (aTopic == "alertshow") {
|
||||
ok(!this.alertShow, "Alert should not be shown more than once");
|
||||
this.alertShow = true;
|
||||
} else {
|
||||
is(aTopic, "alertfinished", "Checking the topic for a finished notification");
|
||||
SimpleTest.finish();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
function runTest() {
|
||||
const Cc = SpecialPowers.Cc;
|
||||
const Ci = SpecialPowers.Ci;
|
||||
|
||||
if (!("@mozilla.org/alerts-service;1" in Cc)) {
|
||||
todo(false, "Alerts service does not exist in this application");
|
||||
return;
|
||||
}
|
||||
|
||||
ok(true, "Alerts service exists in this application");
|
||||
|
||||
var notifier;
|
||||
try {
|
||||
notifier = Cc["@mozilla.org/alerts-service;1"].
|
||||
getService(Ci.nsIAlertsService);
|
||||
ok(true, "Alerts service is available");
|
||||
} catch (ex) {
|
||||
todo(false,
|
||||
"Alerts service is not available.", ex);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
var alertName = "fiorello";
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
notifier.showAlertNotification(null, "Notification test",
|
||||
"Surprise! I'm here to test notifications!",
|
||||
false, "foobarcookie", observer, alertName);
|
||||
ok(true, "showAlertNotification() succeeded. Waiting for notification...");
|
||||
|
||||
if ("@mozilla.org/system-alerts-service;1" in Cc) {
|
||||
// Notifications are native on OS X 10.8 and later, as well as GNOME
|
||||
// Shell with libnotify (bug 1236036). These notifications persist in the
|
||||
// Notification Center, and only fire the `alertfinished` event when
|
||||
// closed. For platforms where native notifications may be used, we need
|
||||
// to close explicitly to avoid a hang. This also works for XUL
|
||||
// notifications when running this test on OS X < 10.8, or a window
|
||||
// manager like Ubuntu Unity with incomplete libnotify support.
|
||||
notifier.closeAlert(alertName);
|
||||
}
|
||||
} catch (ex) {
|
||||
todo(false, "showAlertNotification() failed.", ex);
|
||||
SimpleTest.finish();
|
||||
}
|
||||
}
|
||||
|
||||
runTest();
|
||||
|
||||
</script>
|
||||
</pre>
|
||||
</body>
|
||||
</html>
|
||||
96
toolkit/components/alerts/test/test_alerts_noobserve.html
Normal file
96
toolkit/components/alerts/test/test_alerts_noobserve.html
Normal file
|
|
@ -0,0 +1,96 @@
|
|||
<!DOCTYPE HTML>
|
||||
<!-- Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ -->
|
||||
<html>
|
||||
<head>
|
||||
<title>Test for Alerts Service</title>
|
||||
<script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
|
||||
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<p id="display"></p>
|
||||
|
||||
<br>Alerts service, without observer "asynchronous" case.
|
||||
<br>
|
||||
<br>A notification should soon appear somewhere.
|
||||
<br>If there has been no crash when the notification (later) disappears, assume all is good.
|
||||
|
||||
<pre id="test">
|
||||
<script class="testbody" type="text/javascript">
|
||||
|
||||
const Cc = SpecialPowers.Cc;
|
||||
const Ci = SpecialPowers.Ci;
|
||||
|
||||
const chromeScript = SpecialPowers.loadChromeScript(_ => {
|
||||
const { utils: Cu } = Components;
|
||||
|
||||
Cu.import("resource://gre/modules/Services.jsm");
|
||||
Cu.import("resource://gre/modules/Timer.jsm");
|
||||
|
||||
function anyXULAlertsVisible() {
|
||||
var windows = Services.wm.getEnumerator("alert:alert");
|
||||
return windows.hasMoreElements();
|
||||
}
|
||||
|
||||
addMessageListener("anyXULAlertsVisible", anyXULAlertsVisible);
|
||||
|
||||
addMessageListener("waitForAlerts", function waitForAlerts() {
|
||||
if (anyXULAlertsVisible()) {
|
||||
setTimeout(waitForAlerts, 1000);
|
||||
} else {
|
||||
sendAsyncMessage("waitedForAlerts");
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
function waitForAlertsThenFinish() {
|
||||
chromeScript.addMessageListener("waitedForAlerts", function waitedForAlerts() {
|
||||
chromeScript.removeMessageListener("waitedForAlerts", waitedForAlerts);
|
||||
ok(true, "Alert disappeared.");
|
||||
SimpleTest.finish();
|
||||
});
|
||||
chromeScript.sendAsyncMessage("waitForAlerts");
|
||||
}
|
||||
|
||||
function runTest() {
|
||||
if (!("@mozilla.org/alerts-service;1" in Cc)) {
|
||||
todo(false, "Alerts service does not exist in this application");
|
||||
} else {
|
||||
ok(true, "Alerts service exists in this application");
|
||||
|
||||
var notifier;
|
||||
try {
|
||||
notifier = Cc["@mozilla.org/alerts-service;1"].
|
||||
getService(Ci.nsIAlertsService);
|
||||
ok(true, "Alerts service is available");
|
||||
} catch (ex) {
|
||||
todo(false, "Alerts service is not available.", ex);
|
||||
}
|
||||
|
||||
if (notifier) {
|
||||
try {
|
||||
notifier.showAlertNotification(null, "Notification test",
|
||||
"This notification has no observer");
|
||||
ok(true, "showAlertNotification() succeeded");
|
||||
} catch (ex) {
|
||||
todo(false, "showAlertNotification() failed.", ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
SimpleTest.requestFlakyTimeout("untriaged");
|
||||
|
||||
// sendSyncMessage returns an array of arrays: the outer array is from the
|
||||
// message manager, and the inner array is from the chrome script's listeners.
|
||||
// See the comment in test_SpecialPowersLoadChromeScript.html.
|
||||
var [[alertsVisible]] = chromeScript.sendSyncMessage("anyXULAlertsVisible");
|
||||
ok(!alertsVisible, "Alerts should not be present at the start of the test.");
|
||||
runTest();
|
||||
setTimeout(waitForAlertsThenFinish, 1000);
|
||||
</script>
|
||||
</pre>
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -0,0 +1,168 @@
|
|||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<head>
|
||||
<title>Test for alerts with requireInteraction</title>
|
||||
<script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
|
||||
<script type="text/javascript" src="/tests/SimpleTest/SpawnTask.js"></script>
|
||||
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
|
||||
</head>
|
||||
<body>
|
||||
<pre id="test">
|
||||
<script class="testbody" type="text/javascript">
|
||||
|
||||
const Cc = SpecialPowers.Cc;
|
||||
const Ci = SpecialPowers.Ci;
|
||||
|
||||
const chromeScript = SpecialPowers.loadChromeScript(_ => {
|
||||
const { utils: Cu } = Components;
|
||||
|
||||
Cu.import("resource://gre/modules/Services.jsm");
|
||||
Cu.import("resource://gre/modules/Timer.jsm");
|
||||
|
||||
addMessageListener("waitForXULAlert", function() {
|
||||
var timer = setTimeout(function() {
|
||||
Services.ww.unregisterNotification(windowObserver);
|
||||
sendAsyncMessage("waitForXULAlert", false);
|
||||
}, 2000);
|
||||
|
||||
var windowObserver = function(aSubject, aTopic, aData) {
|
||||
if (aTopic != "domwindowopened") {
|
||||
return;
|
||||
}
|
||||
|
||||
var win = aSubject.QueryInterface(Components.interfaces.nsIDOMWindow);
|
||||
win.addEventListener("load", function onLoad() {
|
||||
win.removeEventListener("load", onLoad);
|
||||
let windowType = win.document.documentElement.getAttribute("windowtype");
|
||||
if (windowType == "alert:alert") {
|
||||
clearTimeout(timer);
|
||||
Services.ww.unregisterNotification(windowObserver);
|
||||
|
||||
sendAsyncMessage("waitForXULAlert", true);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
Services.ww.registerNotification(windowObserver);
|
||||
});
|
||||
});
|
||||
|
||||
var cookie = 0;
|
||||
function promiseCreateXULAlert(alertService, listener, name) {
|
||||
return new Promise(resolve => {
|
||||
chromeScript.addMessageListener("waitForXULAlert", function waitedForAlert(result) {
|
||||
chromeScript.removeMessageListener("waitForXULAlert", waitedForAlert);
|
||||
resolve(result);
|
||||
});
|
||||
|
||||
chromeScript.sendAsyncMessage("waitForXULAlert");
|
||||
alertService.showAlertNotification(null, "title", "body",
|
||||
true, cookie++, listener, name, null, null, null,
|
||||
null, false, true);
|
||||
});
|
||||
}
|
||||
|
||||
add_task(function* test_require_interaction() {
|
||||
if (!("@mozilla.org/alerts-service;1" in Cc)) {
|
||||
todo(false, "Alerts service does not exist in this application.");
|
||||
return;
|
||||
}
|
||||
|
||||
ok(true, "Alerts service exists in this application.");
|
||||
|
||||
var alertService;
|
||||
try {
|
||||
alertService = Cc["@mozilla.org/alerts-service;1"].getService(Ci.nsIAlertsService);
|
||||
ok(true, "Alerts service is available.");
|
||||
} catch (ex) {
|
||||
todo(false, "Alerts service is not available.");
|
||||
return;
|
||||
}
|
||||
|
||||
yield SpecialPowers.pushPrefEnv({"set": [
|
||||
[ "dom.webnotifications.requireinteraction.enabled", true ],
|
||||
[ "dom.webnotifications.requireinteraction.count", 2 ]
|
||||
]});
|
||||
|
||||
var expectedSequence = [
|
||||
"first show",
|
||||
"second show",
|
||||
"second finished",
|
||||
"second replacement show",
|
||||
"third finished",
|
||||
"first finished",
|
||||
"third replacement show",
|
||||
"second replacement finished",
|
||||
"third replacement finished"
|
||||
];
|
||||
|
||||
var actualSequence = [];
|
||||
|
||||
function createAlertListener(name, showCallback, finishCallback) {
|
||||
return (subject, topic, data) => {
|
||||
if (topic == "alertshow") {
|
||||
actualSequence.push(name + " show");
|
||||
if (showCallback) {
|
||||
showCallback();
|
||||
}
|
||||
} else if (topic == "alertfinished") {
|
||||
actualSequence.push(name + " finished");
|
||||
if (finishCallback) {
|
||||
finishCallback();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var xulAlertCreated = yield promiseCreateXULAlert(alertService,
|
||||
createAlertListener("first"), "first");
|
||||
if (!xulAlertCreated) {
|
||||
ok(true, "Platform does not use XUL alerts.");
|
||||
alertService.closeAlert("first");
|
||||
return;
|
||||
}
|
||||
|
||||
xulAlertCreated = yield promiseCreateXULAlert(alertService,
|
||||
createAlertListener("second"), "second");
|
||||
ok(xulAlertCreated, "Create XUL alert");
|
||||
|
||||
// Replace second alert
|
||||
xulAlertCreated = yield promiseCreateXULAlert(alertService,
|
||||
createAlertListener("second replacement"), "second");
|
||||
ok(xulAlertCreated, "Create XUL alert");
|
||||
|
||||
var testFinishResolve;
|
||||
var testFinishPromise = new Promise((resolve) => { testFinishResolve = resolve; });
|
||||
|
||||
xulAlertCreated = yield promiseCreateXULAlert(alertService,
|
||||
createAlertListener("third"), "third"),
|
||||
ok(!xulAlertCreated, "XUL alert should not be visible");
|
||||
|
||||
// Replace the not-yet-visible third alert.
|
||||
xulAlertCreated = yield promiseCreateXULAlert(alertService,
|
||||
createAlertListener("third replacement",
|
||||
function showCallback() {
|
||||
alertService.closeAlert("second");
|
||||
alertService.closeAlert("third");
|
||||
},
|
||||
function finishCallback() {
|
||||
// Check actual sequence of alert events compared to expected sequence.
|
||||
for (var i = 0; i < actualSequence.length; i++) {
|
||||
is(actualSequence[i], expectedSequence[i],
|
||||
"Alert callback at index " + i + " should be in expected order.");
|
||||
}
|
||||
|
||||
testFinishResolve();
|
||||
}), "third");
|
||||
|
||||
ok(!xulAlertCreated, "XUL alert should not be visible");
|
||||
|
||||
alertService.closeAlert("first");
|
||||
|
||||
yield testFinishPromise;
|
||||
});
|
||||
|
||||
</script>
|
||||
</pre>
|
||||
</body>
|
||||
</html>
|
||||
118
toolkit/components/alerts/test/test_image.html
Normal file
118
toolkit/components/alerts/test/test_image.html
Normal file
|
|
@ -0,0 +1,118 @@
|
|||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<head>
|
||||
<title>Test for Bug 1233086</title>
|
||||
<script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
|
||||
<script type="text/javascript" src="/tests/SimpleTest/SpawnTask.js"></script>
|
||||
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<p id="display"></p>
|
||||
|
||||
<pre id="test">
|
||||
<script class="testbody" type="text/javascript">
|
||||
|
||||
const Cc = SpecialPowers.Cc;
|
||||
const Ci = SpecialPowers.Ci;
|
||||
const Services = SpecialPowers.Services;
|
||||
|
||||
const imageServerURL = "http://mochi.test:8888/tests/toolkit/components/alerts/test/image_server.sjs";
|
||||
|
||||
function makeAlert(...params) {
|
||||
var alert = Cc["@mozilla.org/alert-notification;1"]
|
||||
.createInstance(Ci.nsIAlertNotification);
|
||||
alert.init(...params);
|
||||
return alert;
|
||||
}
|
||||
|
||||
function promiseImage(alert, timeout = 0, userData = null) {
|
||||
return new Promise(resolve => {
|
||||
var isDone = false;
|
||||
function done(value) {
|
||||
ok(!isDone, "Should call the image listener once");
|
||||
isDone = true;
|
||||
resolve(value);
|
||||
}
|
||||
alert.loadImage(timeout, SpecialPowers.wrapCallbackObject({
|
||||
onImageReady(aUserData, aRequest) {
|
||||
done([true, aRequest, aUserData]);
|
||||
},
|
||||
onImageMissing(aUserData) {
|
||||
done([false, aUserData]);
|
||||
},
|
||||
}), SpecialPowers.wrap(userData));
|
||||
});
|
||||
}
|
||||
|
||||
add_task(function* testContext() {
|
||||
var inUserData = Cc["@mozilla.org/supports-PRInt64;1"]
|
||||
.createInstance(Ci.nsISupportsPRInt64);
|
||||
inUserData.data = 123;
|
||||
|
||||
var alert = makeAlert(null, imageServerURL + "?f=image.png");
|
||||
var [ready, , userData] = yield promiseImage(alert, 0, inUserData);
|
||||
ok(ready, "Should load requested image");
|
||||
is(userData.QueryInterface(Ci.nsISupportsPRInt64).data, 123,
|
||||
"Should pass user data for loaded image");
|
||||
|
||||
alert = makeAlert(null, imageServerURL + "?s=404");
|
||||
[ready, userData] = yield promiseImage(alert, 0, inUserData);
|
||||
ok(!ready, "Should not load missing image");
|
||||
is(userData.QueryInterface(Ci.nsISupportsPRInt64).data, 123,
|
||||
"Should pass user data for missing image");
|
||||
});
|
||||
|
||||
add_task(function* testTimeout() {
|
||||
var alert = makeAlert(null, imageServerURL + "?f=image.png&t=3");
|
||||
var [ready] = yield promiseImage(alert, 1000);
|
||||
ok(!ready, "Should cancel request if timeout fires");
|
||||
|
||||
[ready, request] = yield promiseImage(alert, 45000);
|
||||
ok(ready, "Should load image if request finishes before timeout");
|
||||
});
|
||||
|
||||
add_task(function* testAnimatedGIF() {
|
||||
var alert = makeAlert(null, imageServerURL + "?f=image.gif");
|
||||
var [ready, request] = yield promiseImage(alert);
|
||||
ok(ready, "Should load first animated GIF frame");
|
||||
is(request.mimeType, "image/gif", "Should report correct GIF MIME type");
|
||||
is(request.image.width, 256, "GIF width should be 256px");
|
||||
is(request.image.height, 256, "GIF height should be 256px");
|
||||
});
|
||||
|
||||
add_task(function* testCancel() {
|
||||
var alert = makeAlert(null, imageServerURL + "?f=image.gif&t=180");
|
||||
yield new Promise((resolve, reject) => {
|
||||
var request = alert.loadImage(0, SpecialPowers.wrapCallbackObject({
|
||||
onImageReady() {
|
||||
reject(new Error("Should not load cancelled request"));
|
||||
},
|
||||
onImageMissing() {
|
||||
resolve();
|
||||
},
|
||||
}), null);
|
||||
request.cancel(SpecialPowers.Cr.NS_BINDING_ABORTED);
|
||||
});
|
||||
});
|
||||
|
||||
add_task(function* testMixedContent() {
|
||||
// Loading principal is HTTPS; image URL is HTTP.
|
||||
var origin = "https://mochi.test:8888";
|
||||
var principal = Services.scriptSecurityManager
|
||||
.createCodebasePrincipalFromOrigin(origin);
|
||||
|
||||
var alert = makeAlert(null, imageServerURL + "?f=image.png",
|
||||
null, null, false, null, null, null,
|
||||
null, principal);
|
||||
var [ready, request] = yield promiseImage(alert);
|
||||
ok(ready, "Should load cross-protocol image");
|
||||
is(request.mimeType, "image/png", "Should report correct MIME type");
|
||||
is(request.image.width, 32, "Width should be 32px");
|
||||
is(request.image.height, 32, "Height should be 32px");
|
||||
});
|
||||
|
||||
</script>
|
||||
</pre>
|
||||
</body>
|
||||
</html>
|
||||
103
toolkit/components/alerts/test/test_multiple_alerts.html
Normal file
103
toolkit/components/alerts/test/test_multiple_alerts.html
Normal file
|
|
@ -0,0 +1,103 @@
|
|||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<head>
|
||||
<title>Test for multiple alerts</title>
|
||||
<script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
|
||||
<script type="text/javascript" src="/tests/SimpleTest/SpawnTask.js"></script>
|
||||
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
|
||||
</head>
|
||||
<body>
|
||||
<pre id="test">
|
||||
<script class="testbody" type="text/javascript">
|
||||
|
||||
const Cc = SpecialPowers.Cc;
|
||||
const Ci = SpecialPowers.Ci;
|
||||
|
||||
const chromeScript = SpecialPowers.loadChromeScript(_ => {
|
||||
Components.utils.import("resource://gre/modules/Services.jsm");
|
||||
Components.utils.import("resource://gre/modules/Timer.jsm");
|
||||
|
||||
const alertService = Components.classes["@mozilla.org/alerts-service;1"]
|
||||
.getService(Components.interfaces.nsIAlertsService);
|
||||
|
||||
addMessageListener("waitForPosition", function() {
|
||||
var timer = setTimeout(function() {
|
||||
Services.ww.unregisterNotification(windowObserver);
|
||||
sendAsyncMessage("waitedForPosition", null);
|
||||
}, 2000);
|
||||
|
||||
var windowObserver = function(aSubject, aTopic, aData) {
|
||||
if (aTopic != "domwindowopened") {
|
||||
return;
|
||||
}
|
||||
|
||||
// Alerts are implemented using XUL.
|
||||
clearTimeout(timer);
|
||||
|
||||
Services.ww.unregisterNotification(windowObserver);
|
||||
|
||||
var win = aSubject.QueryInterface(Components.interfaces.nsIDOMWindow);
|
||||
win.addEventListener("pageshow", function onPageShow() {
|
||||
win.removeEventListener("pageshow", onPageShow, false);
|
||||
|
||||
var x = win.screenX;
|
||||
var y = win.screenY;
|
||||
|
||||
win.addEventListener("pagehide", function onPageHide() {
|
||||
win.removeEventListener("pagehide", onPageHide, false);
|
||||
sendAsyncMessage("waitedForPosition", { x, y });
|
||||
}, false);
|
||||
|
||||
alertService.closeAlert();
|
||||
}, false);
|
||||
};
|
||||
|
||||
Services.ww.registerNotification(windowObserver);
|
||||
});
|
||||
});
|
||||
|
||||
function promiseAlertPosition(alertService) {
|
||||
return new Promise(resolve => {
|
||||
chromeScript.addMessageListener("waitedForPosition", function waitedForPosition(result) {
|
||||
chromeScript.removeMessageListener("waitedForPosition", waitedForPosition);
|
||||
resolve(result);
|
||||
});
|
||||
chromeScript.sendAsyncMessage("waitForPosition");
|
||||
|
||||
alertService.showAlertNotification(null, "title", "body");
|
||||
ok(true, "Alert shown.");
|
||||
});
|
||||
}
|
||||
|
||||
add_task(function* test_multiple_alerts() {
|
||||
if (!("@mozilla.org/alerts-service;1" in Cc)) {
|
||||
todo(false, "Alerts service does not exist in this application.");
|
||||
return;
|
||||
}
|
||||
|
||||
ok(true, "Alerts service exists in this application.");
|
||||
|
||||
var alertService;
|
||||
try {
|
||||
alertService = Cc["@mozilla.org/alerts-service;1"].getService(Ci.nsIAlertsService);
|
||||
ok(true, "Alerts service is available.");
|
||||
} catch (ex) {
|
||||
todo(false, "Alerts service is not available.");
|
||||
return;
|
||||
}
|
||||
|
||||
var firstAlertPosition = yield promiseAlertPosition(alertService);
|
||||
if (!firstAlertPosition) {
|
||||
ok(true, "Platform does not use XUL alerts.");
|
||||
return;
|
||||
}
|
||||
|
||||
var secondAlertPosition = yield promiseAlertPosition(alertService);
|
||||
is(secondAlertPosition.x, firstAlertPosition.x, "Second alert should be opened in the same position.");
|
||||
is(secondAlertPosition.y, firstAlertPosition.y, "Second alert should be opened in the same position.");
|
||||
});
|
||||
|
||||
</script>
|
||||
</pre>
|
||||
</body>
|
||||
</html>
|
||||
122
toolkit/components/alerts/test/test_principal.html
Normal file
122
toolkit/components/alerts/test/test_principal.html
Normal file
|
|
@ -0,0 +1,122 @@
|
|||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<head>
|
||||
<title>Test for Bug 1202933</title>
|
||||
<script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
|
||||
<script type="text/javascript" src="/tests/SimpleTest/SpawnTask.js"></script>
|
||||
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<p id="display"></p>
|
||||
|
||||
<pre id="test">
|
||||
<script class="testbody" type="text/javascript">
|
||||
|
||||
const Cc = SpecialPowers.Cc;
|
||||
const Ci = SpecialPowers.Ci;
|
||||
const Services = SpecialPowers.Services;
|
||||
|
||||
const notifier = Cc["@mozilla.org/alerts-service;1"]
|
||||
.getService(Ci.nsIAlertsService);
|
||||
|
||||
const chromeScript = SpecialPowers.loadChromeScript(_ => {
|
||||
Components.utils.import("resource://gre/modules/Services.jsm");
|
||||
|
||||
addMessageListener("anyXULAlertsVisible", function() {
|
||||
var windows = Services.wm.getEnumerator("alert:alert");
|
||||
return windows.hasMoreElements();
|
||||
});
|
||||
|
||||
addMessageListener("getAlertSource", function() {
|
||||
var alertWindows = Services.wm.getEnumerator("alert:alert");
|
||||
if (!alertWindows) {
|
||||
return null;
|
||||
}
|
||||
var alertWindow = alertWindows.getNext();
|
||||
return alertWindow.document.getElementById("alertSourceLabel").getAttribute("value");
|
||||
});
|
||||
});
|
||||
|
||||
function notify(alertName, principal) {
|
||||
return new Promise((resolve, reject) => {
|
||||
var source;
|
||||
function observe(subject, topic, data) {
|
||||
if (topic == "alertclickcallback") {
|
||||
reject(new Error("Alerts should not be clicked during test"));
|
||||
} else if (topic == "alertshow") {
|
||||
source = chromeScript.sendSyncMessage("getAlertSource")[0][0];
|
||||
notifier.closeAlert(alertName);
|
||||
} else {
|
||||
is(topic, "alertfinished", "Should hide alert");
|
||||
resolve(source);
|
||||
}
|
||||
}
|
||||
notifier.showAlertNotification(null, "Notification test",
|
||||
"Surprise! I'm here to test notifications!",
|
||||
false, alertName, observe, alertName,
|
||||
null, null, null, principal);
|
||||
if (SpecialPowers.Services.appinfo.OS == "Darwin") {
|
||||
notifier.closeAlert(alertName);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function* testNoPrincipal() {
|
||||
var source = yield notify("noPrincipal", null);
|
||||
ok(!source, "Should omit source without principal");
|
||||
}
|
||||
|
||||
function* testSystemPrincipal() {
|
||||
var principal = Services.scriptSecurityManager.getSystemPrincipal();
|
||||
var source = yield notify("systemPrincipal", principal);
|
||||
ok(!source, "Should omit source for system principal");
|
||||
}
|
||||
|
||||
function* testNullPrincipal() {
|
||||
var principal = Services.scriptSecurityManager.createNullPrincipal({});
|
||||
var source = yield notify("nullPrincipal", principal);
|
||||
ok(!source, "Should omit source for null principal");
|
||||
}
|
||||
|
||||
function* testNodePrincipal() {
|
||||
var principal = SpecialPowers.wrap(document).nodePrincipal;
|
||||
var source = yield notify("nodePrincipal", principal);
|
||||
|
||||
var stringBundle = Services.strings.createBundle(
|
||||
"chrome://alerts/locale/alert.properties"
|
||||
);
|
||||
var localizedSource = stringBundle.formatStringFromName(
|
||||
"source.label", [principal.URI.hostPort], 1);
|
||||
is(source, localizedSource, "Should include source for node principal");
|
||||
}
|
||||
|
||||
function runTest() {
|
||||
if (!("@mozilla.org/alerts-service;1" in Cc)) {
|
||||
todo(false, "Alerts service does not exist in this application");
|
||||
return;
|
||||
}
|
||||
|
||||
if ("@mozilla.org/system-alerts-service;1" in Cc) {
|
||||
todo(false, "Native alerts service exists in this application");
|
||||
return;
|
||||
}
|
||||
|
||||
ok(true, "Alerts service exists in this application");
|
||||
|
||||
// sendSyncMessage returns an array of arrays. See the comments in
|
||||
// test_alerts_noobserve.html and test_SpecialPowersLoadChromeScript.html.
|
||||
var [[alertsVisible]] = chromeScript.sendSyncMessage("anyXULAlertsVisible");
|
||||
ok(!alertsVisible, "Alerts should not be present at the start of the test.");
|
||||
|
||||
add_task(testNoPrincipal);
|
||||
add_task(testSystemPrincipal);
|
||||
add_task(testNullPrincipal);
|
||||
add_task(testNodePrincipal);
|
||||
}
|
||||
|
||||
runTest();
|
||||
</script>
|
||||
</pre>
|
||||
</body>
|
||||
</html>
|
||||
Loading…
Add table
Add a link
Reference in a new issue