diff options
Diffstat (limited to 'dom/media/autoplay/GVAutoplayPermissionRequest.cpp')
-rw-r--r-- | dom/media/autoplay/GVAutoplayPermissionRequest.cpp | 236 |
1 files changed, 236 insertions, 0 deletions
diff --git a/dom/media/autoplay/GVAutoplayPermissionRequest.cpp b/dom/media/autoplay/GVAutoplayPermissionRequest.cpp new file mode 100644 index 0000000000..600c7d291e --- /dev/null +++ b/dom/media/autoplay/GVAutoplayPermissionRequest.cpp @@ -0,0 +1,236 @@ +/* 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 "GVAutoplayPermissionRequest.h" + +#include "mozilla/dom/HTMLMediaElement.h" +#include "mozilla/Logging.h" +#include "mozilla/StaticPrefs_media.h" +#include "nsGlobalWindowInner.h" + +mozilla::LazyLogModule gGVAutoplayRequestLog("GVAutoplay"); + +namespace mozilla::dom { + +using RType = GVAutoplayRequestType; +using RStatus = GVAutoplayRequestStatus; + +const char* ToGVRequestTypeStr(RType aType) { + switch (aType) { + case RType::eINAUDIBLE: + return "inaudible"; + case RType::eAUDIBLE: + return "audible"; + default: + MOZ_ASSERT_UNREACHABLE("Invalid request type."); + return "invalid"; + } +} + +const char* ToGVRequestStatusStr(RStatus aStatus) { + switch (aStatus) { + case RStatus::eUNKNOWN: + return "Unknown"; + case RStatus::eALLOWED: + return "Allowed"; + case RStatus::eDENIED: + return "Denied"; + case RStatus::ePENDING: + return "Pending"; + default: + MOZ_ASSERT_UNREACHABLE("Invalid status."); + return "Invalid"; + } +} + +// avoid redefined macro in unified build +#undef REQUEST_LOG +#define REQUEST_LOG(msg, ...) \ + if (MOZ_LOG_TEST(gGVAutoplayRequestLog, mozilla::LogLevel::Debug)) { \ + MOZ_LOG(gGVAutoplayRequestLog, LogLevel::Debug, \ + ("Request=%p, Type=%s, " msg, this, \ + ToGVRequestTypeStr(this->mType), ##__VA_ARGS__)); \ + } + +#undef LOG +#define LOG(msg, ...) \ + MOZ_LOG(gGVAutoplayRequestLog, LogLevel::Debug, (msg, ##__VA_ARGS__)) + +static RStatus GetRequestStatus(BrowsingContext* aContext, RType aType) { + MOZ_ASSERT(aContext); + AssertIsOnMainThread(); + return aType == RType::eAUDIBLE + ? aContext->GetGVAudibleAutoplayRequestStatus() + : aContext->GetGVInaudibleAutoplayRequestStatus(); +} + +// This is copied from the value of `media.geckoview.autoplay.request.testing`. +enum class TestRequest : uint32_t { + ePromptAsNormal = 0, + eAllowAll = 1, + eDenyAll = 2, + eAllowAudible = 3, + eDenyAudible = 4, + eAllowInAudible = 5, + eDenyInAudible = 6, + eLeaveAllPending = 7, +}; + +NS_IMPL_CYCLE_COLLECTION_INHERITED(GVAutoplayPermissionRequest, + ContentPermissionRequestBase) + +NS_IMPL_ISUPPORTS_CYCLE_COLLECTION_INHERITED_0(GVAutoplayPermissionRequest, + ContentPermissionRequestBase) + +/* static */ +void GVAutoplayPermissionRequest::CreateRequest(nsGlobalWindowInner* aWindow, + BrowsingContext* aContext, + GVAutoplayRequestType aType) { + RefPtr<GVAutoplayPermissionRequest> request = + new GVAutoplayPermissionRequest(aWindow, aContext, aType); + request->SetRequestStatus(RStatus::ePENDING); + const TestRequest testingPref = static_cast<TestRequest>( + StaticPrefs::media_geckoview_autoplay_request_testing()); + if (testingPref != TestRequest::ePromptAsNormal) { + LOG("Create testing request, tesing value=%u", + static_cast<uint32_t>(testingPref)); + if (testingPref == TestRequest::eAllowAll || + (testingPref == TestRequest::eAllowAudible && + aType == RType::eAUDIBLE) || + (testingPref == TestRequest::eAllowInAudible && + aType == RType::eINAUDIBLE)) { + request->Allow(JS::UndefinedHandleValue); + } else if (testingPref == TestRequest::eDenyAll || + (testingPref == TestRequest::eDenyAudible && + aType == RType::eAUDIBLE) || + (testingPref == TestRequest::eDenyInAudible && + aType == RType::eINAUDIBLE)) { + request->Cancel(); + } + } else { + LOG("Dispatch async request"); + request->RequestDelayedTask( + aWindow->EventTargetFor(TaskCategory::Other), + GVAutoplayPermissionRequest::DelayedTaskType::Request); + } +} + +GVAutoplayPermissionRequest::GVAutoplayPermissionRequest( + nsGlobalWindowInner* aWindow, BrowsingContext* aContext, RType aType) + : ContentPermissionRequestBase(aWindow->GetPrincipal(), aWindow, + ""_ns, // No testing pref used in this class + aType == RType::eAUDIBLE + ? "autoplay-media-audible"_ns + : "autoplay-media-inaudible"_ns), + mType(aType), + mContext(aContext) { + MOZ_ASSERT(mContext); + REQUEST_LOG("Request created"); +} + +GVAutoplayPermissionRequest::~GVAutoplayPermissionRequest() { + REQUEST_LOG("Request destroyed"); + // If user doesn't response to the request before it gets destroyed (ex. + // request dismissed, tab closed, naviagation to a new page), then we should + // treat it as a denial. + if (mContext) { + Cancel(); + } +} + +void GVAutoplayPermissionRequest::SetRequestStatus(RStatus aStatus) { + REQUEST_LOG("SetRequestStatus, new status=%s", ToGVRequestStatusStr(aStatus)); + MOZ_ASSERT(mContext); + AssertIsOnMainThread(); + if (mType == RType::eAUDIBLE) { + // Return value of setting synced field should be checked. See bug 1656492. + Unused << mContext->SetGVAudibleAutoplayRequestStatus(aStatus); + } else { + // Return value of setting synced field should be checked. See bug 1656492. + Unused << mContext->SetGVInaudibleAutoplayRequestStatus(aStatus); + } +} + +NS_IMETHODIMP +GVAutoplayPermissionRequest::Cancel() { + MOZ_ASSERT(mContext, "Do not call 'Cancel()' twice!"); + // As the process of replying of the request is an async task, the status + // might have be reset at the time we get the result from parent process. + // Ex. if the page got closed or naviagated immediately after user replied to + // the request. Therefore, the status should be either `pending` or `unknown`. + const RStatus status = GetRequestStatus(mContext, mType); + REQUEST_LOG("Cancel, current status=%s", ToGVRequestStatusStr(status)); + MOZ_ASSERT(status == RStatus::ePENDING || status == RStatus::eUNKNOWN); + if ((status == RStatus::ePENDING) && !mContext->IsDiscarded()) { + SetRequestStatus(RStatus::eDENIED); + } + mContext = nullptr; + return NS_OK; +} + +NS_IMETHODIMP +GVAutoplayPermissionRequest::Allow(JS::Handle<JS::Value> aChoices) { + MOZ_ASSERT(mContext, "Do not call 'Allow()' twice!"); + // As the process of replying of the request is an async task, the status + // might have be reset at the time we get the result from parent process. + // Ex. if the page got closed or naviagated immediately after user replied to + // the request. Therefore, the status should be either `pending` or `unknown`. + const RStatus status = GetRequestStatus(mContext, mType); + REQUEST_LOG("Allow, current status=%s", ToGVRequestStatusStr(status)); + MOZ_ASSERT(status == RStatus::ePENDING || status == RStatus::eUNKNOWN); + if (status == RStatus::ePENDING) { + SetRequestStatus(RStatus::eALLOWED); + } + mContext = nullptr; + return NS_OK; +} + +/* static */ +void GVAutoplayPermissionRequestor::AskForPermissionIfNeeded( + nsPIDOMWindowInner* aWindow) { + LOG("Requestor, AskForPermissionIfNeeded"); + if (!aWindow) { + return; + } + + // The request is used for content permission, so it's no need to create a + // content request in parent process if we're in e10s. + if (XRE_IsE10sParentProcess()) { + return; + } + + if (!StaticPrefs::media_geckoview_autoplay_request()) { + return; + } + + LOG("Requestor, check status to decide if we need to create the new request"); + // The request status is stored in top-level browsing context only. + RefPtr<BrowsingContext> context = aWindow->GetBrowsingContext()->Top(); + if (!HasEverAskForRequest(context, RType::eAUDIBLE)) { + CreateAsyncRequest(aWindow, context, RType::eAUDIBLE); + } + if (!HasEverAskForRequest(context, RType::eINAUDIBLE)) { + CreateAsyncRequest(aWindow, context, RType::eINAUDIBLE); + } +} + +/* static */ +bool GVAutoplayPermissionRequestor::HasEverAskForRequest( + BrowsingContext* aContext, RType aType) { + return GetRequestStatus(aContext, aType) != RStatus::eUNKNOWN; +} + +/* static */ +void GVAutoplayPermissionRequestor::CreateAsyncRequest( + nsPIDOMWindowInner* aWindow, BrowsingContext* aContext, + GVAutoplayRequestType aType) { + nsGlobalWindowInner* innerWindow = nsGlobalWindowInner::Cast(aWindow); + if (!innerWindow || !innerWindow->GetPrincipal()) { + return; + } + + GVAutoplayPermissionRequest::CreateRequest(innerWindow, aContext, aType); +} + +} // namespace mozilla::dom |