/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* vim: set ts=8 sts=2 et sw=2 tw=80: */ /* 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/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() = default; 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, bool aSilent, const nsTArray& aVibrate) { mName = aName; mImageURL = aImageURL; mTitle = aTitle; mText = aText; mTextClickable = aTextClickable; mCookie = aCookie; mDir = aDir; mLang = aLang; mData = aData; mPrincipal = aPrincipal; mInPrivateBrowsing = aInPrivateBrowsing; mRequireInteraction = aRequireInteraction; mSilent = aSilent; mVibrate = aVibrate.Clone(); return NS_OK; } NS_IMETHODIMP AlertNotification::SetActions( const nsTArray>& aActions) { mActions = aActions.Clone(); 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; } auto* basePrin = BasePrincipal::Cast(mPrincipal); return basePrin->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::GetSilent(bool* aSilent) { *aSilent = mSilent; return NS_OK; } NS_IMETHODIMP AlertNotification::GetVibrate(nsTArray& aVibrate) { aVibrate = mVibrate.Clone(); return NS_OK; } NS_IMETHODIMP AlertNotification::GetActions(nsTArray>& aActions) { aActions = mActions.Clone(); return NS_OK; } NS_IMETHODIMP AlertNotification::GetSource(nsAString& aSource) { nsAlertsUtils::GetSourceHostPort(mPrincipal, aSource); return NS_OK; } NS_IMETHODIMP AlertNotification::GetOpaqueRelaunchData(nsAString& aOpaqueRelaunchData) { aOpaqueRelaunchData = mOpaqueRelaunchData; return NS_OK; } NS_IMETHODIMP AlertNotification::SetOpaqueRelaunchData(const nsAString& aOpaqueRelaunchData) { mOpaqueRelaunchData = aOpaqueRelaunchData; 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 imageURI; NS_NewURI(getter_AddRefs(imageURI), mImageURL); if (!imageURI) { return aListener->OnImageMissing(aUserData); } RefPtr request = new AlertImageRequest( imageURI, mPrincipal, mInPrivateBrowsing, aTimeout, aListener, aUserData); request->Start(); request.forget(aRequest); return NS_OK; } 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(nsINamed) 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); } } void 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)) { NotifyMissing(); return; } // 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 image; rv = aRequest->GetImage(getter_AddRefs(image)); if (NS_WARN_IF(NS_FAILED(rv) || !image)) { NotifyMissing(); return; } // 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_HIGH_QUALITY_SCALING); } return; } if (aType == imgINotificationObserver::FRAME_COMPLETE) { return NotifyComplete(); } } NS_IMETHODIMP AlertImageRequest::Notify(nsITimer* aTimer) { MOZ_ASSERT(aTimer == mTimer); return NotifyMissing(); } NS_IMETHODIMP AlertImageRequest::GetName(nsACString& aName) { aName.AssignLiteral("AlertImageRequest"); return NS_OK; } 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) { rv = NS_NewTimerWithCallback(getter_AddRefs(mTimer), 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 = nsIRequest::LOAD_NORMAL; if (mInPrivateBrowsing) { loadFlags = nsIRequest::LOAD_ANONYMOUS; } rv = il->LoadImageXPCOM( mURI, nullptr, nullptr, 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 listener = std::move(mListener)) { nsresult rv = listener->OnImageMissing(mUserData); NS_RELEASE_THIS(); return rv; } return NS_OK; } void AlertImageRequest::NotifyComplete() { if (mTimer) { mTimer->Cancel(); mTimer = nullptr; } if (nsCOMPtr listener = std::move(mListener)) { listener->OnImageReady(mUserData, mRequest); NS_RELEASE_THIS(); } } } // namespace mozilla