summaryrefslogtreecommitdiffstats
path: root/dom/media/autoplay/GVAutoplayPermissionRequest.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'dom/media/autoplay/GVAutoplayPermissionRequest.cpp')
-rw-r--r--dom/media/autoplay/GVAutoplayPermissionRequest.cpp236
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..beb3fb59fc
--- /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->SerialEventTarget(),
+ 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