GVAutoplayPermissionRequest.cpp (9534B)
1 /* This Source Code Form is subject to the terms of the Mozilla Public 2 * License, v. 2.0. If a copy of the MPL was not distributed with this file, 3 * You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 5 #include "GVAutoplayPermissionRequest.h" 6 7 #include "mozilla/Logging.h" 8 #include "mozilla/StaticPrefs_media.h" 9 #include "mozilla/dom/HTMLMediaElement.h" 10 #include "nsGlobalWindowInner.h" 11 12 mozilla::LazyLogModule gGVAutoplayRequestLog("GVAutoplay"); 13 14 namespace mozilla::dom { 15 16 using RType = GVAutoplayRequestType; 17 using RStatus = GVAutoplayRequestStatus; 18 19 // avoid redefined macro in unified build 20 #undef REQUEST_LOG 21 #define REQUEST_LOG(msg, ...) \ 22 if (MOZ_LOG_TEST(gGVAutoplayRequestLog, mozilla::LogLevel::Debug)) { \ 23 MOZ_LOG(gGVAutoplayRequestLog, LogLevel::Debug, \ 24 ("Request=%p, Type=%s, " msg, this, \ 25 EnumValueToString(this->mType), ##__VA_ARGS__)); \ 26 } 27 28 #undef LOG 29 #define LOG(msg, ...) \ 30 MOZ_LOG(gGVAutoplayRequestLog, LogLevel::Debug, (msg, ##__VA_ARGS__)) 31 32 static RStatus GetRequestStatus(BrowsingContext* aContext, RType aType) { 33 MOZ_ASSERT(aContext); 34 AssertIsOnMainThread(); 35 return aType == RType::eAUDIBLE 36 ? aContext->GetGVAudibleAutoplayRequestStatus() 37 : aContext->GetGVInaudibleAutoplayRequestStatus(); 38 } 39 40 // This is copied from the value of `media.geckoview.autoplay.request.testing`. 41 enum class TestRequest : uint32_t { 42 ePromptAsNormal = 0, 43 eAllowAll = 1, 44 eDenyAll = 2, 45 eAllowAudible = 3, 46 eDenyAudible = 4, 47 eAllowInAudible = 5, 48 eDenyInAudible = 6, 49 eLeaveAllPending = 7, 50 eAllowAllAsync = 8, 51 }; 52 53 NS_IMPL_CYCLE_COLLECTION_INHERITED(GVAutoplayPermissionRequest, 54 ContentPermissionRequestBase) 55 56 NS_IMPL_ISUPPORTS_CYCLE_COLLECTION_INHERITED_0(GVAutoplayPermissionRequest, 57 ContentPermissionRequestBase) 58 59 /* static */ 60 void GVAutoplayPermissionRequest::CreateRequest(nsGlobalWindowInner* aWindow, 61 BrowsingContext* aContext, 62 GVAutoplayRequestType aType) { 63 RefPtr<GVAutoplayPermissionRequest> request = 64 new GVAutoplayPermissionRequest(aWindow, aContext, aType); 65 request->SetRequestStatus(RStatus::ePENDING); 66 const TestRequest testingPref = static_cast<TestRequest>( 67 StaticPrefs::media_geckoview_autoplay_request_testing()); 68 if (testingPref != TestRequest::ePromptAsNormal) { 69 LOG("Create testing request, tesing value=%u", 70 static_cast<uint32_t>(testingPref)); 71 if (testingPref == TestRequest::eAllowAllAsync) { 72 request->RequestDelayedTask( 73 aWindow->SerialEventTarget(), 74 GVAutoplayPermissionRequest::DelayedTaskType::Allow); 75 } else if (testingPref == TestRequest::eAllowAll || 76 (testingPref == TestRequest::eAllowAudible && 77 aType == RType::eAUDIBLE) || 78 (testingPref == TestRequest::eAllowInAudible && 79 aType == RType::eINAUDIBLE)) { 80 request->Allow(JS::UndefinedHandleValue); 81 } else if (testingPref == TestRequest::eDenyAll || 82 (testingPref == TestRequest::eDenyAudible && 83 aType == RType::eAUDIBLE) || 84 (testingPref == TestRequest::eDenyInAudible && 85 aType == RType::eINAUDIBLE)) { 86 request->Cancel(); 87 } 88 } else { 89 LOG("Dispatch async request"); 90 request->RequestDelayedTask( 91 aWindow->SerialEventTarget(), 92 GVAutoplayPermissionRequest::DelayedTaskType::Request); 93 } 94 } 95 96 GVAutoplayPermissionRequest::GVAutoplayPermissionRequest( 97 nsGlobalWindowInner* aWindow, BrowsingContext* aContext, RType aType) 98 : ContentPermissionRequestBase(aWindow->GetPrincipal(), aWindow, 99 ""_ns, // No testing pref used in this class 100 aType == RType::eAUDIBLE 101 ? "autoplay-media-audible"_ns 102 : "autoplay-media-inaudible"_ns), 103 mType(aType), 104 mContext(aContext) { 105 MOZ_ASSERT(mContext); 106 REQUEST_LOG("Request created"); 107 } 108 109 GVAutoplayPermissionRequest::~GVAutoplayPermissionRequest() { 110 REQUEST_LOG("Request destroyed"); 111 // If user doesn't response to the request before it gets destroyed (ex. 112 // request dismissed, tab closed, naviagation to a new page), then we should 113 // treat it as a denial. 114 if (mContext) { 115 Cancel(); 116 } 117 } 118 119 void GVAutoplayPermissionRequest::SetRequestStatus(RStatus aStatus) { 120 REQUEST_LOG("SetRequestStatus, new status=%s", EnumValueToString(aStatus)); 121 MOZ_ASSERT(mContext); 122 AssertIsOnMainThread(); 123 if (mType == RType::eAUDIBLE) { 124 // Return value of setting synced field should be checked. See bug 1656492. 125 (void)mContext->SetGVAudibleAutoplayRequestStatus(aStatus); 126 } else { 127 // Return value of setting synced field should be checked. See bug 1656492. 128 (void)mContext->SetGVInaudibleAutoplayRequestStatus(aStatus); 129 } 130 } 131 132 NS_IMETHODIMP 133 GVAutoplayPermissionRequest::Cancel() { 134 MOZ_ASSERT(mContext, "Do not call 'Cancel()' twice!"); 135 // As the process of replying to the request is an async task, the status 136 // might have been reset at the time we get the result from parent process. 137 // Ex. if the page got closed or navigated immediately after user replied to 138 // the request. Therefore, the status should be either `pending` or `unknown`. 139 // Additionally, we tolerate `canceled` if the request was already canceled. 140 // See Bug 1996123 for details on the multiple cancel. 141 const RStatus status = GetRequestStatus(mContext, mType); 142 REQUEST_LOG("Cancel, current status=%s", EnumValueToString(status)); 143 MOZ_ASSERT(status == RStatus::ePENDING || status == RStatus::eDENIED || 144 status == RStatus::eUNKNOWN); 145 if ((status == RStatus::ePENDING) && !mContext->IsDiscarded()) { 146 SetRequestStatus(RStatus::eDENIED); 147 } 148 mContext = nullptr; 149 return NS_OK; 150 } 151 152 NS_IMETHODIMP 153 GVAutoplayPermissionRequest::Allow(JS::Handle<JS::Value> aChoices) { 154 MOZ_ASSERT(mContext, "Do not call 'Allow()' twice!"); 155 // As the process of replying to the request is an async task, the status 156 // might have been reset at the time we get the result from parent process. 157 // Ex. if the page got closed or navigated immediately after user replied to 158 // the request. Therefore, the status should be either `pending` or `unknown`. 159 // Additionally, we tolerate `allowed` if the request was already allowed. 160 // See Bug 1996123 for details on the multiple allow. 161 const RStatus status = GetRequestStatus(mContext, mType); 162 REQUEST_LOG("Allow, current status=%s", EnumValueToString(status)); 163 MOZ_ASSERT(status == RStatus::ePENDING || status == RStatus::eALLOWED || 164 status == RStatus::eUNKNOWN); 165 if (status == RStatus::ePENDING) { 166 SetRequestStatus(RStatus::eALLOWED); 167 // Permission grant may arrive late and elements may be suspended. 168 // We message to wake them up and resume downloading data if needed. 169 nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService(); 170 if (obs) { 171 obs->NotifyObservers(ToSupports(mWindow), kGVAutoplayAllowedTopic, 172 /* no extra string data */ nullptr); 173 } 174 } 175 mContext = nullptr; 176 return NS_OK; 177 } 178 179 /* static */ 180 void GVAutoplayPermissionRequestor::AskForPermissionIfNeeded( 181 nsPIDOMWindowInner* aWindow) { 182 LOG("Requestor, AskForPermissionIfNeeded"); 183 if (!aWindow) { 184 return; 185 } 186 187 // The request is used for content permission, so it's no need to create a 188 // content request in parent process if we're in e10s. 189 if (XRE_IsE10sParentProcess()) { 190 return; 191 } 192 193 if (!StaticPrefs::media_geckoview_autoplay_request()) { 194 return; 195 } 196 197 LOG("Requestor, check status to decide if we need to create the new request"); 198 // The request status is stored in top-level browsing context only. 199 RefPtr<BrowsingContext> context = aWindow->GetBrowsingContext()->Top(); 200 if (!HasEverAskForRequest(context, RType::eAUDIBLE)) { 201 CreateAsyncRequest(aWindow, context, RType::eAUDIBLE); 202 } 203 if (!HasEverAskForRequest(context, RType::eINAUDIBLE)) { 204 CreateAsyncRequest(aWindow, context, RType::eINAUDIBLE); 205 } 206 } 207 208 /* static */ 209 bool GVAutoplayPermissionRequestor::HasEverAskForRequest( 210 BrowsingContext* aContext, RType aType) { 211 return GetRequestStatus(aContext, aType) != RStatus::eUNKNOWN; 212 } 213 214 /* static */ 215 bool GVAutoplayPermissionRequestor::HasUnresolvedRequest( 216 nsPIDOMWindowInner* aWindow) { 217 if (!aWindow) { 218 return false; 219 } 220 221 RefPtr<BrowsingContext> context = aWindow->GetBrowsingContext()->Top(); 222 auto gvAudible = context->GetGVAudibleAutoplayRequestStatus(); 223 auto gvInaudible = context->GetGVInaudibleAutoplayRequestStatus(); 224 return (gvAudible == GVAutoplayRequestStatus::eUNKNOWN) || 225 (gvAudible == GVAutoplayRequestStatus::ePENDING) || 226 (gvInaudible == GVAutoplayRequestStatus::eUNKNOWN) || 227 (gvInaudible == GVAutoplayRequestStatus::ePENDING); 228 } 229 230 /* static */ 231 void GVAutoplayPermissionRequestor::CreateAsyncRequest( 232 nsPIDOMWindowInner* aWindow, BrowsingContext* aContext, 233 GVAutoplayRequestType aType) { 234 nsGlobalWindowInner* innerWindow = nsGlobalWindowInner::Cast(aWindow); 235 if (!innerWindow || !innerWindow->GetPrincipal()) { 236 return; 237 } 238 239 GVAutoplayPermissionRequest::CreateRequest(innerWindow, aContext, aType); 240 } 241 242 } // namespace mozilla::dom