/* -*- 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 "ErrorList.h" #include "mozilla/AlreadyAddRefed.h" #include "mozilla/Assertions.h" #include "mozilla/dom/Document.h" #include "mozilla/dom/Event.h" #include "mozilla/dom/FeaturePolicyUtils.h" #include "mozilla/dom/Navigator.h" #include "mozilla/dom/Promise.h" #include "mozilla/dom/WakeLockBinding.h" #include "mozilla/Hal.h" #include "nsCOMPtr.h" #include "nsError.h" #include "nsIGlobalObject.h" #include "nsISupports.h" #include "nsPIDOMWindow.h" #include "nsContentPermissionHelper.h" #include "nscore.h" #include "WakeLock.h" #include "WakeLockJS.h" #include "WakeLockSentinel.h" namespace mozilla::dom { nsLiteralCString WakeLockJS::GetRequestErrorMessage(RequestError aRv) { switch (aRv) { case RequestError::DocInactive: return "The requesting document is inactive."_ns; case RequestError::DocHidden: return "The requesting document is hidden."_ns; case RequestError::PolicyDisallowed: return "A permissions policy does not allow screen-wake-lock for the requesting document."_ns; case RequestError::PrefDisabled: return "The pref dom.screenwakelock.enabled is disabled."_ns; case RequestError::InternalFailure: return "A browser-internal error occured."_ns; case RequestError::PermissionDenied: return "Permission to request screen-wake-lock was denied."_ns; default: MOZ_ASSERT_UNREACHABLE("Unknown error reason"); return "Unknown error"_ns; } } // https://w3c.github.io/screen-wake-lock/#the-request-method steps 2-5 WakeLockJS::RequestError WakeLockJS::WakeLockAllowedForDocument( Document* aDoc) { if (!aDoc) { return RequestError::InternalFailure; } // Step 2. check policy-controlled feature screen-wake-lock if (!FeaturePolicyUtils::IsFeatureAllowed(aDoc, u"screen-wake-lock"_ns)) { return RequestError::PolicyDisallowed; } // Step 4 check doc active if (!aDoc->IsActive()) { return RequestError::DocInactive; } // Step 5. check doc visible if (aDoc->Hidden()) { return RequestError::DocHidden; } return RequestError::Success; } // https://w3c.github.io/screen-wake-lock/#dfn-applicable-wake-lock static bool IsWakeLockApplicable(WakeLockType aType) { // only currently supported wake lock type return aType == WakeLockType::Screen; } // https://w3c.github.io/screen-wake-lock/#dfn-release-a-wake-lock void ReleaseWakeLock(Document* aDoc, WakeLockSentinel* aLock, WakeLockType aType) { MOZ_ASSERT(aLock); MOZ_ASSERT(aDoc); RefPtr kungFuDeathGrip = aLock; aDoc->ActiveWakeLocks(aType).Remove(aLock); aLock->NotifyLockReleased(); } NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(WakeLockJS, mWindow) NS_IMPL_CYCLE_COLLECTING_ADDREF(WakeLockJS) NS_IMPL_CYCLE_COLLECTING_RELEASE(WakeLockJS) NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(WakeLockJS) NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIDOMEventListener) NS_INTERFACE_MAP_ENTRY(nsIDocumentActivity) NS_INTERFACE_MAP_END WakeLockJS::WakeLockJS(nsPIDOMWindowInner* aWindow) : mWindow(aWindow) { AttachListeners(); } WakeLockJS::~WakeLockJS() { DetachListeners(); } nsISupports* WakeLockJS::GetParentObject() const { return mWindow; } JSObject* WakeLockJS::WrapObject(JSContext* aCx, JS::Handle aGivenProto) { return WakeLock_Binding::Wrap(aCx, this, aGivenProto); } // https://w3c.github.io/screen-wake-lock/#the-request-method Step 7.3 Result, WakeLockJS::RequestError> WakeLockJS::Obtain(WakeLockType aType) { // Step 7.3.1. check visibility again nsCOMPtr doc = mWindow->GetExtantDoc(); if (!doc) { return Err(RequestError::InternalFailure); } if (doc->Hidden()) { return Err(RequestError::DocHidden); } // Step 7.3.3. let lock be a new WakeLockSentinel RefPtr lock = MakeRefPtr(mWindow->AsGlobal(), aType); // Step 7.3.2. acquire a wake lock if (IsWakeLockApplicable(aType)) { lock->AcquireActualLock(); } // Steps 7.3.4. append lock to locks doc->ActiveWakeLocks(aType).Insert(lock); return lock.forget(); } // https://w3c.github.io/screen-wake-lock/#the-request-method already_AddRefed WakeLockJS::Request(WakeLockType aType, ErrorResult& aRv) { nsCOMPtr global = mWindow->AsGlobal(); RefPtr promise = Promise::Create(global, aRv); NS_ENSURE_FALSE(aRv.Failed(), nullptr); // Steps 1-5 nsCOMPtr doc = mWindow->GetExtantDoc(); RequestError rv = WakeLockAllowedForDocument(doc); if (rv != RequestError::Success) { promise->MaybeRejectWithNotAllowedError(GetRequestErrorMessage(rv)); return promise.forget(); } // For now, we don't check the permission as we always grant the lock // Step 7.3. Queue a task NS_DispatchToMainThread(NS_NewRunnableFunction( "ObtainWakeLock", [aType, promise, self = RefPtr(this)]() { auto lockOrErr = self->Obtain(aType); if (lockOrErr.isOk()) { RefPtr lock = lockOrErr.unwrap(); promise->MaybeResolve(lock); } else { promise->MaybeRejectWithNotAllowedError( GetRequestErrorMessage(lockOrErr.unwrapErr())); } })); return promise.forget(); } void WakeLockJS::AttachListeners() { nsCOMPtr doc = mWindow->GetExtantDoc(); MOZ_ASSERT(doc); DebugOnly rv = doc->AddSystemEventListener(u"visibilitychange"_ns, this, true, false); MOZ_ASSERT(NS_SUCCEEDED(rv)); doc->RegisterActivityObserver(ToSupports(this)); } void WakeLockJS::DetachListeners() { if (mWindow) { if (nsCOMPtr doc = mWindow->GetExtantDoc()) { doc->RemoveSystemEventListener(u"visibilitychange"_ns, this, true); doc->UnregisterActivityObserver(ToSupports(this)); } } } void WakeLockJS::NotifyOwnerDocumentActivityChanged() { nsCOMPtr doc = mWindow->GetExtantDoc(); MOZ_ASSERT(doc); if (!doc->IsActive()) { doc->UnlockAllWakeLocks(WakeLockType::Screen); } } NS_IMETHODIMP WakeLockJS::HandleEvent(Event* aEvent) { nsAutoString type; aEvent->GetType(type); if (type.EqualsLiteral("visibilitychange")) { nsCOMPtr doc = do_QueryInterface(aEvent->GetTarget()); NS_ENSURE_STATE(doc); if (doc->Hidden()) { doc->UnlockAllWakeLocks(WakeLockType::Screen); } } return NS_OK; } } // namespace mozilla::dom