summaryrefslogtreecommitdiffstats
path: root/dom/power
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
commit26a029d407be480d791972afb5975cf62c9360a6 (patch)
treef435a8308119effd964b339f76abb83a57c29483 /dom/power
parentInitial commit. (diff)
downloadfirefox-26a029d407be480d791972afb5975cf62c9360a6.tar.xz
firefox-26a029d407be480d791972afb5975cf62c9360a6.zip
Adding upstream version 124.0.1.upstream/124.0.1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'dom/power')
-rw-r--r--dom/power/PowerManagerService.cpp138
-rw-r--r--dom/power/PowerManagerService.h57
-rw-r--r--dom/power/WakeLock.cpp181
-rw-r--r--dom/power/WakeLock.h74
-rw-r--r--dom/power/WakeLockJS.cpp288
-rw-r--r--dom/power/WakeLockJS.h103
-rw-r--r--dom/power/WakeLockSentinel.cpp100
-rw-r--r--dom/power/WakeLockSentinel.h79
-rw-r--r--dom/power/components.conf17
-rw-r--r--dom/power/moz.build39
-rw-r--r--dom/power/nsIDOMWakeLockListener.idl29
-rw-r--r--dom/power/nsIPowerManagerService.idl32
-rw-r--r--dom/power/nsIWakeLock.idl12
-rw-r--r--dom/power/tests/mochitest.toml9
-rw-r--r--dom/power/tests/test_dynamic_pref_change.html34
-rw-r--r--dom/power/tests/test_wakelock_default_permission.html20
16 files changed, 1212 insertions, 0 deletions
diff --git a/dom/power/PowerManagerService.cpp b/dom/power/PowerManagerService.cpp
new file mode 100644
index 0000000000..ad3eadce8e
--- /dev/null
+++ b/dom/power/PowerManagerService.cpp
@@ -0,0 +1,138 @@
+/* -*- 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/dom/ContentParent.h"
+#include "mozilla/Hal.h"
+#include "mozilla/HalWakeLock.h"
+#include "mozilla/ClearOnShutdown.h"
+#include "mozilla/ModuleUtils.h"
+#include "mozilla/Preferences.h"
+#include "nsIDOMWakeLockListener.h"
+#include "PowerManagerService.h"
+#include "WakeLock.h"
+
+// For _exit().
+#ifdef XP_WIN
+# include <process.h>
+#else
+# include <unistd.h>
+#endif
+
+namespace mozilla::dom::power {
+
+using namespace hal;
+
+NS_IMPL_ISUPPORTS(PowerManagerService, nsIPowerManagerService)
+
+/* static */
+StaticRefPtr<PowerManagerService> PowerManagerService::sSingleton;
+
+/* static */
+already_AddRefed<PowerManagerService> PowerManagerService::GetInstance() {
+ if (!sSingleton) {
+ sSingleton = new PowerManagerService();
+ sSingleton->Init();
+ ClearOnShutdown(&sSingleton);
+ }
+
+ RefPtr<PowerManagerService> service = sSingleton.get();
+ return service.forget();
+}
+
+void PowerManagerService::Init() { RegisterWakeLockObserver(this); }
+
+PowerManagerService::~PowerManagerService() {
+ UnregisterWakeLockObserver(this);
+}
+
+void PowerManagerService::ComputeWakeLockState(
+ const WakeLockInformation& aWakeLockInfo, nsAString& aState) {
+ WakeLockState state = hal::ComputeWakeLockState(aWakeLockInfo.numLocks(),
+ aWakeLockInfo.numHidden());
+ switch (state) {
+ case WAKE_LOCK_STATE_UNLOCKED:
+ aState.AssignLiteral("unlocked");
+ break;
+ case WAKE_LOCK_STATE_HIDDEN:
+ aState.AssignLiteral("locked-background");
+ break;
+ case WAKE_LOCK_STATE_VISIBLE:
+ aState.AssignLiteral("locked-foreground");
+ break;
+ }
+}
+
+void PowerManagerService::Notify(const WakeLockInformation& aWakeLockInfo) {
+ nsAutoString state;
+ ComputeWakeLockState(aWakeLockInfo, state);
+
+ /**
+ * Copy the listeners list before we walk through the callbacks
+ * because the callbacks may install new listeners. We expect no
+ * more than one listener per window, so it shouldn't be too long.
+ */
+ const CopyableAutoTArray<nsCOMPtr<nsIDOMMozWakeLockListener>, 2> listeners =
+ mWakeLockListeners;
+
+ for (uint32_t i = 0; i < listeners.Length(); ++i) {
+ listeners[i]->Callback(aWakeLockInfo.topic(), state);
+ }
+}
+
+NS_IMETHODIMP
+PowerManagerService::AddWakeLockListener(nsIDOMMozWakeLockListener* aListener) {
+ if (mWakeLockListeners.Contains(aListener)) return NS_OK;
+
+ mWakeLockListeners.AppendElement(aListener);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+PowerManagerService::RemoveWakeLockListener(
+ nsIDOMMozWakeLockListener* aListener) {
+ mWakeLockListeners.RemoveElement(aListener);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+PowerManagerService::GetWakeLockState(const nsAString& aTopic,
+ nsAString& aState) {
+ WakeLockInformation info;
+ GetWakeLockInfo(aTopic, &info);
+
+ ComputeWakeLockState(info, aState);
+
+ return NS_OK;
+}
+
+already_AddRefed<WakeLock> PowerManagerService::NewWakeLock(
+ const nsAString& aTopic, nsPIDOMWindowInner* aWindow,
+ mozilla::ErrorResult& aRv) {
+ RefPtr<WakeLock> wakelock = new WakeLock();
+ aRv = wakelock->Init(aTopic, aWindow);
+ if (aRv.Failed()) {
+ return nullptr;
+ }
+
+ return wakelock.forget();
+}
+
+NS_IMETHODIMP
+PowerManagerService::NewWakeLock(const nsAString& aTopic,
+ mozIDOMWindow* aWindow,
+ nsIWakeLock** aWakeLock) {
+ mozilla::ErrorResult rv;
+ RefPtr<WakeLock> wakelock =
+ NewWakeLock(aTopic, nsPIDOMWindowInner::From(aWindow), rv);
+ if (rv.Failed()) {
+ return rv.StealNSResult();
+ }
+
+ wakelock.forget(aWakeLock);
+ return NS_OK;
+}
+
+} // namespace mozilla::dom::power
diff --git a/dom/power/PowerManagerService.h b/dom/power/PowerManagerService.h
new file mode 100644
index 0000000000..1522342201
--- /dev/null
+++ b/dom/power/PowerManagerService.h
@@ -0,0 +1,57 @@
+/* -*- 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/. */
+#ifndef mozilla_dom_power_PowerManagerService_h
+#define mozilla_dom_power_PowerManagerService_h
+
+#include "nsCOMPtr.h"
+#include "nsHashKeys.h"
+#include "nsTArray.h"
+#include "nsIDOMWakeLockListener.h"
+#include "nsIPowerManagerService.h"
+#include "mozilla/HalWakeLockInformation.h"
+#include "mozilla/StaticPtr.h"
+#include "mozilla/dom/WakeLock.h"
+
+namespace mozilla::dom {
+
+class ContentParent;
+
+namespace power {
+
+class PowerManagerService : public nsIPowerManagerService,
+ public hal::WakeLockObserver {
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIPOWERMANAGERSERVICE
+
+ PowerManagerService() = default;
+
+ static already_AddRefed<PowerManagerService> GetInstance();
+
+ void Init();
+
+ // Implement WakeLockObserver
+ void Notify(const hal::WakeLockInformation& aWakeLockInfo) override;
+
+ already_AddRefed<WakeLock> NewWakeLock(const nsAString& aTopic,
+ nsPIDOMWindowInner* aWindow,
+ mozilla::ErrorResult& aRv);
+
+ private:
+ ~PowerManagerService();
+
+ void ComputeWakeLockState(const hal::WakeLockInformation& aWakeLockInfo,
+ nsAString& aState);
+
+ static StaticRefPtr<PowerManagerService> sSingleton;
+
+ nsTArray<nsCOMPtr<nsIDOMMozWakeLockListener>> mWakeLockListeners;
+};
+
+} // namespace power
+} // namespace mozilla::dom
+
+#endif // mozilla_dom_power_PowerManagerService_h
diff --git a/dom/power/WakeLock.cpp b/dom/power/WakeLock.cpp
new file mode 100644
index 0000000000..b381b3c627
--- /dev/null
+++ b/dom/power/WakeLock.cpp
@@ -0,0 +1,181 @@
+/* -*- 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 "WakeLock.h"
+#include "mozilla/dom/ContentParent.h"
+#include "mozilla/dom/Event.h" // for Event
+#include "mozilla/Hal.h"
+#include "mozilla/HalWakeLock.h"
+#include "nsError.h"
+#include "mozilla/dom/Document.h"
+#include "nsPIDOMWindow.h"
+#include "nsIPropertyBag2.h"
+
+using namespace mozilla::hal;
+
+namespace mozilla::dom {
+
+NS_INTERFACE_MAP_BEGIN(WakeLock)
+ NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIDOMEventListener)
+ NS_INTERFACE_MAP_ENTRY(nsIDOMEventListener)
+ NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference)
+ NS_INTERFACE_MAP_ENTRY(nsIWakeLock)
+NS_INTERFACE_MAP_END
+
+NS_IMPL_ADDREF(WakeLock)
+NS_IMPL_RELEASE(WakeLock)
+
+WakeLock::~WakeLock() {
+ DoUnlock();
+ DetachEventListener();
+}
+
+nsresult WakeLock::Init(const nsAString& aTopic, nsPIDOMWindowInner* aWindow) {
+ // Don't Init() a WakeLock twice.
+ MOZ_ASSERT(mTopic.IsEmpty());
+
+ if (aTopic.IsEmpty()) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ mTopic.Assign(aTopic);
+
+ mWindow = do_GetWeakReference(aWindow);
+
+ /**
+ * Null windows are allowed. A wake lock without associated window
+ * is always considered invisible.
+ */
+ if (aWindow) {
+ nsCOMPtr<Document> doc = aWindow->GetExtantDoc();
+ NS_ENSURE_STATE(doc);
+ mHidden = doc->Hidden();
+ }
+
+ AttachEventListener();
+ DoLock();
+
+ return NS_OK;
+}
+
+void WakeLock::DoLock() {
+ if (!mLocked) {
+ // Change the flag immediately to prevent recursive reentering
+ mLocked = true;
+
+ hal::ModifyWakeLock(
+ mTopic, hal::WAKE_LOCK_ADD_ONE,
+ mHidden ? hal::WAKE_LOCK_ADD_ONE : hal::WAKE_LOCK_NO_CHANGE);
+ }
+}
+
+void WakeLock::DoUnlock() {
+ if (mLocked) {
+ // Change the flag immediately to prevent recursive reentering
+ mLocked = false;
+
+ hal::ModifyWakeLock(
+ mTopic, hal::WAKE_LOCK_REMOVE_ONE,
+ mHidden ? hal::WAKE_LOCK_REMOVE_ONE : hal::WAKE_LOCK_NO_CHANGE);
+ }
+}
+
+void WakeLock::AttachEventListener() {
+ if (nsCOMPtr<nsPIDOMWindowInner> window = do_QueryReferent(mWindow)) {
+ nsCOMPtr<Document> doc = window->GetExtantDoc();
+ if (doc) {
+ doc->AddSystemEventListener(u"visibilitychange"_ns, this,
+ /* useCapture = */ true,
+ /* wantsUntrusted = */ false);
+
+ nsCOMPtr<EventTarget> target = do_QueryInterface(window);
+ target->AddSystemEventListener(u"pagehide"_ns, this,
+ /* useCapture = */ true,
+ /* wantsUntrusted = */ false);
+ target->AddSystemEventListener(u"pageshow"_ns, this,
+ /* useCapture = */ true,
+ /* wantsUntrusted = */ false);
+ }
+ }
+}
+
+void WakeLock::DetachEventListener() {
+ if (nsCOMPtr<nsPIDOMWindowInner> window = do_QueryReferent(mWindow)) {
+ nsCOMPtr<Document> doc = window->GetExtantDoc();
+ if (doc) {
+ doc->RemoveSystemEventListener(u"visibilitychange"_ns, this,
+ /* useCapture = */ true);
+ nsCOMPtr<EventTarget> target = do_QueryInterface(window);
+ target->RemoveSystemEventListener(u"pagehide"_ns, this,
+ /* useCapture = */ true);
+ target->RemoveSystemEventListener(u"pageshow"_ns, this,
+ /* useCapture = */ true);
+ }
+ }
+}
+
+void WakeLock::Unlock(ErrorResult& aRv) {
+ /*
+ * We throw NS_ERROR_DOM_INVALID_STATE_ERR on double unlock.
+ */
+ if (!mLocked) {
+ aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
+ return;
+ }
+
+ DoUnlock();
+ DetachEventListener();
+}
+
+void WakeLock::GetTopic(nsAString& aTopic) { aTopic.Assign(mTopic); }
+
+NS_IMETHODIMP
+WakeLock::HandleEvent(Event* aEvent) {
+ nsAutoString type;
+ aEvent->GetType(type);
+
+ if (type.EqualsLiteral("visibilitychange")) {
+ nsCOMPtr<Document> doc = do_QueryInterface(aEvent->GetTarget());
+ NS_ENSURE_STATE(doc);
+
+ bool oldHidden = mHidden;
+ mHidden = doc->Hidden();
+
+ if (mLocked && oldHidden != mHidden) {
+ hal::ModifyWakeLock(
+ mTopic, hal::WAKE_LOCK_NO_CHANGE,
+ mHidden ? hal::WAKE_LOCK_ADD_ONE : hal::WAKE_LOCK_REMOVE_ONE);
+ }
+
+ return NS_OK;
+ }
+
+ if (type.EqualsLiteral("pagehide")) {
+ DoUnlock();
+ return NS_OK;
+ }
+
+ if (type.EqualsLiteral("pageshow")) {
+ DoLock();
+ return NS_OK;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+WakeLock::Unlock() {
+ ErrorResult error;
+ Unlock(error);
+ return error.StealNSResult();
+}
+
+nsPIDOMWindowInner* WakeLock::GetParentObject() const {
+ nsCOMPtr<nsPIDOMWindowInner> window = do_QueryInterface(mWindow);
+ return window;
+}
+
+} // namespace mozilla::dom
diff --git a/dom/power/WakeLock.h b/dom/power/WakeLock.h
new file mode 100644
index 0000000000..0f14c3d7d4
--- /dev/null
+++ b/dom/power/WakeLock.h
@@ -0,0 +1,74 @@
+/* -*- 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/. */
+
+#ifndef mozilla_dom_power_WakeLock_h
+#define mozilla_dom_power_WakeLock_h
+
+#include "nsCOMPtr.h"
+#include "nsIDOMEventListener.h"
+#include "nsIWakeLock.h"
+#include "nsString.h"
+#include "nsWeakReference.h"
+#include "nsWrapperCache.h"
+
+class nsPIDOMWindowInner;
+
+namespace mozilla {
+class ErrorResult;
+
+namespace dom {
+class Document;
+
+class WakeLock final : public nsIDOMEventListener,
+ public nsSupportsWeakReference,
+ public nsIWakeLock {
+ public:
+ NS_DECL_NSIDOMEVENTLISTENER
+ NS_DECL_NSIWAKELOCK
+
+ NS_DECL_ISUPPORTS
+
+ // Note: WakeLock lives for the lifetime of the document in order to avoid
+ // exposing GC behavior to pages. This means that
+ // |var foo = navigator.requestWakeLock('cpu'); foo = null;|
+ // doesn't unlock the 'cpu' resource.
+
+ WakeLock() = default;
+
+ // Initialize this wake lock on behalf of the given window. Null windows are
+ // allowed; a lock without an associated window is always considered
+ // invisible.
+ nsresult Init(const nsAString& aTopic, nsPIDOMWindowInner* aWindow);
+
+ // WebIDL methods
+
+ nsPIDOMWindowInner* GetParentObject() const;
+
+ void GetTopic(nsAString& aTopic);
+
+ void Unlock(ErrorResult& aRv);
+
+ private:
+ virtual ~WakeLock();
+
+ void DoUnlock();
+ void DoLock();
+ void AttachEventListener();
+ void DetachEventListener();
+
+ bool mLocked = false;
+ bool mHidden = true;
+
+ nsString mTopic;
+
+ // window that this was created for. Weak reference.
+ nsWeakPtr mWindow;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_power_WakeLock_h
diff --git a/dom/power/WakeLockJS.cpp b/dom/power/WakeLockJS.cpp
new file mode 100644
index 0000000000..50e4a716c0
--- /dev/null
+++ b/dom/power/WakeLockJS.cpp
@@ -0,0 +1,288 @@
+/* -*- 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/Logging.h"
+#include "mozilla/dom/Document.h"
+#include "mozilla/dom/Event.h"
+#include "mozilla/dom/EventTarget.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 "mozilla/StaticPrefs_dom.h"
+#include "nsCOMPtr.h"
+#include "nsCRT.h"
+#include "nsError.h"
+#include "nsIGlobalObject.h"
+#include "nsISupports.h"
+#include "nsPIDOMWindow.h"
+#include "nsContentPermissionHelper.h"
+#include "nsServiceManagerUtils.h"
+#include "nscore.h"
+#include "WakeLock.h"
+#include "WakeLockJS.h"
+#include "WakeLockSentinel.h"
+
+namespace mozilla::dom {
+
+static mozilla::LazyLogModule sLogger("ScreenWakeLock");
+
+#define MIN_BATTERY_LEVEL 0.05
+
+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 3. Deny wake lock for user agent specific reasons
+ if (!StaticPrefs::dom_screenwakelock_enabled()) {
+ return RequestError::PrefDisabled;
+ }
+
+ // 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) {
+ hal::BatteryInformation batteryInfo;
+ hal::GetCurrentBatteryInformation(&batteryInfo);
+ if (batteryInfo.level() <= MIN_BATTERY_LEVEL && !batteryInfo.charging()) {
+ return false;
+ }
+
+ // 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<WakeLockSentinel> kungFuDeathGrip = aLock;
+ aDoc->ActiveWakeLocks(aType).Remove(aLock);
+ aLock->NotifyLockReleased();
+ MOZ_LOG(sLogger, LogLevel::Debug, ("Released wake lock sentinel"));
+}
+
+NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_CLASS(WakeLockJS)
+
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(WakeLockJS)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mWindow)
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(WakeLockJS)
+ tmp->DetachListeners();
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mWindow)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+
+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_ENTRY(nsIObserver)
+ NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference)
+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<JSObject*> aGivenProto) {
+ return WakeLock_Binding::Wrap(aCx, this, aGivenProto);
+}
+
+// https://w3c.github.io/screen-wake-lock/#the-request-method Step 7.3
+Result<already_AddRefed<WakeLockSentinel>, WakeLockJS::RequestError>
+WakeLockJS::Obtain(WakeLockType aType) {
+ // Step 7.3.1. check visibility again
+ nsCOMPtr<Document> 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<WakeLockSentinel> lock =
+ MakeRefPtr<WakeLockSentinel>(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<Promise> WakeLockJS::Request(WakeLockType aType,
+ ErrorResult& aRv) {
+ MOZ_LOG(sLogger, LogLevel::Debug, ("Received request for wake lock"));
+ nsCOMPtr<nsIGlobalObject> global = mWindow->AsGlobal();
+
+ RefPtr<Promise> promise = Promise::Create(global, aRv);
+ NS_ENSURE_FALSE(aRv.Failed(), nullptr);
+
+ // Steps 1-5
+ nsCOMPtr<Document> 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<WakeLockJS>(this)]() {
+ auto lockOrErr = self->Obtain(aType);
+ if (lockOrErr.isOk()) {
+ RefPtr<WakeLockSentinel> lock = lockOrErr.unwrap();
+ promise->MaybeResolve(lock);
+ MOZ_LOG(sLogger, LogLevel::Debug,
+ ("Resolved promise with wake lock sentinel"));
+ } else {
+ promise->MaybeRejectWithNotAllowedError(
+ GetRequestErrorMessage(lockOrErr.unwrapErr()));
+ }
+ }));
+
+ return promise.forget();
+}
+
+void WakeLockJS::AttachListeners() {
+ nsCOMPtr<Document> doc = mWindow->GetExtantDoc();
+ MOZ_ASSERT(doc);
+ DebugOnly<nsresult> rv =
+ doc->AddSystemEventListener(u"visibilitychange"_ns, this, true, false);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ doc->RegisterActivityObserver(ToSupports(this));
+
+ hal::RegisterBatteryObserver(this);
+
+ nsCOMPtr<nsIPrefBranch> prefBranch = do_GetService(NS_PREFSERVICE_CONTRACTID);
+ MOZ_ASSERT(prefBranch);
+ rv = prefBranch->AddObserver("dom.screenwakelock.enabled", this, true);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+}
+
+void WakeLockJS::DetachListeners() {
+ if (mWindow) {
+ if (nsCOMPtr<Document> doc = mWindow->GetExtantDoc()) {
+ doc->RemoveSystemEventListener(u"visibilitychange"_ns, this, true);
+
+ doc->UnregisterActivityObserver(ToSupports(this));
+ }
+ }
+
+ hal::UnregisterBatteryObserver(this);
+
+ if (nsCOMPtr<nsIPrefBranch> prefBranch =
+ do_GetService(NS_PREFSERVICE_CONTRACTID)) {
+ prefBranch->RemoveObserver("dom.screenwakelock.enabled", this);
+ }
+}
+
+NS_IMETHODIMP WakeLockJS::Observe(nsISupports* aSubject, const char* aTopic,
+ const char16_t* aData) {
+ if (nsCRT::strcmp(aTopic, NS_PREFBRANCH_PREFCHANGE_TOPIC_ID) == 0) {
+ if (!StaticPrefs::dom_screenwakelock_enabled()) {
+ nsCOMPtr<Document> doc = mWindow->GetExtantDoc();
+ MOZ_ASSERT(doc);
+ doc->UnlockAllWakeLocks(WakeLockType::Screen);
+ }
+ }
+ return NS_OK;
+}
+
+void WakeLockJS::NotifyOwnerDocumentActivityChanged() {
+ nsCOMPtr<Document> 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<Document> doc = do_QueryInterface(aEvent->GetTarget());
+ NS_ENSURE_STATE(doc);
+
+ if (doc->Hidden()) {
+ doc->UnlockAllWakeLocks(WakeLockType::Screen);
+ }
+ }
+
+ return NS_OK;
+}
+
+void WakeLockJS::Notify(const hal::BatteryInformation& aBatteryInfo) {
+ if (aBatteryInfo.level() > MIN_BATTERY_LEVEL || aBatteryInfo.charging()) {
+ return;
+ }
+ nsCOMPtr<Document> doc = mWindow->GetExtantDoc();
+ MOZ_ASSERT(doc);
+ doc->UnlockAllWakeLocks(WakeLockType::Screen);
+}
+
+} // namespace mozilla::dom
diff --git a/dom/power/WakeLockJS.h b/dom/power/WakeLockJS.h
new file mode 100644
index 0000000000..c6858fdd22
--- /dev/null
+++ b/dom/power/WakeLockJS.h
@@ -0,0 +1,103 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* 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/. */
+
+#ifndef DOM_WAKELOCKJS_H_
+#define DOM_WAKELOCKJS_H_
+
+#include "js/TypeDecls.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/HalBatteryInformation.h"
+#include "mozilla/dom/WakeLockBinding.h"
+#include "nsIDOMEventListener.h"
+#include "nsIDocumentActivity.h"
+#include "nsIObserver.h"
+#include "nsCycleCollectionParticipant.h"
+#include "nsWeakReference.h"
+#include "nsWrapperCache.h"
+
+class nsPIDOMWindowInner;
+
+namespace mozilla::dom {
+
+class Promise;
+class Document;
+class WakeLockSentinel;
+
+} // namespace mozilla::dom
+
+namespace mozilla::dom {
+
+/**
+ * Management class for wake locks held from client scripts.
+ * Instances of this class have two purposes:
+ * - Implement navigator.wakeLock.request which creates a WakeLockSentinel
+ * - Listen for state changes that require all WakeLockSentinel to be released
+ * The WakeLockSentinel objects are held in document.mActiveLocks.
+ *
+ * https://www.w3.org/TR/screen-wake-lock/#the-wakelock-interface
+ */
+class WakeLockJS final : public nsIDOMEventListener,
+ public nsWrapperCache,
+ public hal::BatteryObserver,
+ public nsIDocumentActivity,
+ public nsIObserver,
+ public nsSupportsWeakReference {
+ public:
+ NS_DECL_NSIDOMEVENTLISTENER
+ NS_DECL_NSIDOCUMENTACTIVITY
+ NS_DECL_NSIOBSERVER
+
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_WRAPPERCACHE_CLASS_AMBIGUOUS(WakeLockJS,
+ nsIDOMEventListener)
+
+ public:
+ explicit WakeLockJS(nsPIDOMWindowInner* aWindow);
+
+ protected:
+ ~WakeLockJS();
+
+ public:
+ nsISupports* GetParentObject() const;
+
+ JSObject* WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) override;
+
+ void Notify(const hal::BatteryInformation& aBatteryInfo) override;
+
+ already_AddRefed<Promise> Request(WakeLockType aType, ErrorResult& aRv);
+
+ private:
+ enum class RequestError {
+ Success,
+ DocInactive,
+ DocHidden,
+ PolicyDisallowed,
+ PrefDisabled,
+ InternalFailure,
+ PermissionDenied
+ };
+
+ static nsLiteralCString GetRequestErrorMessage(RequestError aRv);
+
+ static RequestError WakeLockAllowedForDocument(Document* aDoc);
+
+ void AttachListeners();
+ void DetachListeners();
+
+ Result<already_AddRefed<WakeLockSentinel>, RequestError> Obtain(
+ WakeLockType aType);
+
+ RefPtr<nsPIDOMWindowInner> mWindow;
+};
+
+MOZ_CAN_RUN_SCRIPT
+void ReleaseWakeLock(Document* aDoc, WakeLockSentinel* aLock,
+ WakeLockType aType);
+
+} // namespace mozilla::dom
+
+#endif // DOM_WAKELOCKJS_H_
diff --git a/dom/power/WakeLockSentinel.cpp b/dom/power/WakeLockSentinel.cpp
new file mode 100644
index 0000000000..1a0739dbd0
--- /dev/null
+++ b/dom/power/WakeLockSentinel.cpp
@@ -0,0 +1,100 @@
+/* -*- 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/Assertions.h"
+#include "mozilla/Telemetry.h"
+#include "mozilla/TelemetryHistogramEnums.h"
+#include "mozilla/dom/Document.h"
+#include "mozilla/dom/Event.h"
+#include "mozilla/dom/EventBinding.h"
+#include "mozilla/dom/Promise.h"
+#include "mozilla/dom/WakeLockSentinelBinding.h"
+#include "mozilla/Hal.h"
+#include "WakeLockJS.h"
+#include "WakeLockSentinel.h"
+
+namespace mozilla::dom {
+
+JSObject* WakeLockSentinel::WrapObject(JSContext* cx,
+ JS::Handle<JSObject*> aGivenProto) {
+ return WakeLockSentinel_Binding::Wrap(cx, this, aGivenProto);
+}
+
+bool WakeLockSentinel::Released() const { return mReleased; }
+
+void WakeLockSentinel::NotifyLockReleased() {
+ MOZ_ASSERT(!mReleased);
+ mReleased = true;
+
+ Telemetry::AccumulateTimeDelta(Telemetry::SCREENWAKELOCK_HELD_DURATION_MS,
+ mCreationTime);
+
+ hal::BatteryInformation batteryInfo;
+ hal::GetCurrentBatteryInformation(&batteryInfo);
+ if (!batteryInfo.charging()) {
+ uint32_t level = static_cast<uint32_t>(100 * batteryInfo.level());
+ Telemetry::Accumulate(
+ Telemetry::SCREENWAKELOCK_RELEASE_BATTERY_LEVEL_DISCHARGING, level);
+ }
+
+ if (mHoldsActualLock) {
+ MOZ_ASSERT(mType == WakeLockType::Screen);
+ NS_DispatchToMainThread(NS_NewRunnableFunction("ReleaseWakeLock", []() {
+ hal::ModifyWakeLock(u"screen"_ns, hal::WAKE_LOCK_REMOVE_ONE,
+ hal::WAKE_LOCK_NO_CHANGE);
+ }));
+ mHoldsActualLock = false;
+ }
+
+ EventInit init;
+ init.mBubbles = false;
+ init.mCancelable = false;
+ RefPtr<Event> event = Event::Constructor(this, u"release"_ns, init);
+ DispatchTrustedEvent(event);
+}
+
+void WakeLockSentinel::AcquireActualLock() {
+ MOZ_ASSERT(mType == WakeLockType::Screen);
+ MOZ_ASSERT(!mHoldsActualLock);
+ mHoldsActualLock = true;
+ NS_DispatchToMainThread(NS_NewRunnableFunction("AcquireWakeLock", []() {
+ hal::ModifyWakeLock(u"screen"_ns, hal::WAKE_LOCK_ADD_ONE,
+ hal::WAKE_LOCK_NO_CHANGE);
+ }));
+}
+
+// https://w3c.github.io/screen-wake-lock/#the-release-method
+already_AddRefed<Promise> WakeLockSentinel::ReleaseLock(ErrorResult& aRv) {
+ // ReleaseWakeLock will remove this from document.[[ActiveLocks]]
+ RefPtr<WakeLockSentinel> kungFuDeathGrip(this);
+
+ if (!mReleased) {
+ nsCOMPtr<nsIGlobalObject> global = GetOwnerGlobal();
+ if (!global) {
+ aRv.Throw(NS_ERROR_NULL_POINTER);
+ return nullptr;
+ }
+ nsCOMPtr<nsPIDOMWindowInner> window = global->GetAsInnerWindow();
+ if (!window) {
+ aRv.Throw(NS_ERROR_NULL_POINTER);
+ return nullptr;
+ }
+ nsCOMPtr<Document> doc = window->GetExtantDoc();
+ if (!doc) {
+ aRv.Throw(NS_ERROR_NULL_POINTER);
+ return nullptr;
+ }
+ ReleaseWakeLock(doc, this, mType);
+ }
+
+ if (RefPtr<Promise> p =
+ Promise::CreateResolvedWithUndefined(GetOwnerGlobal(), aRv)) {
+ return p.forget();
+ }
+ return nullptr;
+}
+
+} // namespace mozilla::dom
diff --git a/dom/power/WakeLockSentinel.h b/dom/power/WakeLockSentinel.h
new file mode 100644
index 0000000000..f9e79e2c65
--- /dev/null
+++ b/dom/power/WakeLockSentinel.h
@@ -0,0 +1,79 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* 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/. */
+
+#ifndef DOM_WAKELOCKSENTINEL_H_
+#define DOM_WAKELOCKSENTINEL_H_
+
+#include "js/TypeDecls.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/DOMEventTargetHelper.h"
+#include "mozilla/TimeStamp.h"
+#include "mozilla/dom/WakeLockBinding.h"
+
+namespace mozilla::dom {
+
+class Promise;
+
+} // namespace mozilla::dom
+
+namespace mozilla::dom {
+
+class WakeLockSentinel final : public DOMEventTargetHelper {
+ public:
+ WakeLockSentinel(nsIGlobalObject* aOwnerWindow, WakeLockType aType)
+ : DOMEventTargetHelper(aOwnerWindow),
+ mType(aType),
+ mCreationTime(TimeStamp::Now()) {}
+
+ protected:
+ ~WakeLockSentinel() {
+ MOZ_DIAGNOSTIC_ASSERT(mReleased);
+ MOZ_DIAGNOSTIC_ASSERT(!mHoldsActualLock);
+ }
+
+ public:
+ JSObject* WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) override;
+
+ bool Released() const;
+
+ WakeLockType Type() const { return mType; }
+
+ MOZ_CAN_RUN_SCRIPT
+ already_AddRefed<Promise> ReleaseLock(ErrorResult& aRv);
+
+ MOZ_CAN_RUN_SCRIPT
+ void NotifyLockReleased();
+
+ IMPL_EVENT_HANDLER(release);
+
+ // Acquire underlying system wake lock by modifying the HAL wake lock counter
+ void AcquireActualLock();
+
+ private:
+ WakeLockType mType;
+
+ bool mReleased = false;
+
+ /**
+ * To avoid user fingerprinting, WakeLockJS::Request will provide a
+ * WakeLockSentinel even if the lock type is not applicable or cannot be
+ * obtained.
+ * But when releasing this sentinel, we have to know whether
+ * AcquireActualLock was called.
+ *
+ * https://w3c.github.io/screen-wake-lock/#dfn-applicable-wake-lock
+ * https://w3c.github.io/screen-wake-lock/#the-request-method
+ */
+ bool mHoldsActualLock = false;
+
+ // Time when this object was created
+ TimeStamp mCreationTime;
+};
+
+} // namespace mozilla::dom
+
+#endif // DOM_WAKELOCKSENTINEL_H_
diff --git a/dom/power/components.conf b/dom/power/components.conf
new file mode 100644
index 0000000000..0d09757f8d
--- /dev/null
+++ b/dom/power/components.conf
@@ -0,0 +1,17 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# 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/.
+
+Classes = [
+ {
+ 'cid': '{18c2e238-3a0a-4153-89fc-166b3b1465a1}',
+ 'contract_ids': ['@mozilla.org/power/powermanagerservice;1'],
+ 'type': 'mozilla::dom::power::PowerManagerService',
+ 'constructor': 'mozilla::dom::power::PowerManagerService::GetInstance',
+ 'headers': ['mozilla/dom/power/PowerManagerService.h'],
+ 'singleton': True,
+ 'processes': ProcessSelector.ALLOW_IN_GPU_RDD_AND_SOCKET_PROCESS,
+ },
+]
diff --git a/dom/power/moz.build b/dom/power/moz.build
new file mode 100644
index 0000000000..5f04a3247d
--- /dev/null
+++ b/dom/power/moz.build
@@ -0,0 +1,39 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# 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/.
+
+with Files("**"):
+ BUG_COMPONENT = ("Core", "DOM: Core & HTML")
+
+XPIDL_SOURCES += [
+ "nsIDOMWakeLockListener.idl",
+ "nsIPowerManagerService.idl",
+ "nsIWakeLock.idl",
+]
+
+XPIDL_MODULE = "dom_power"
+
+XPCOM_MANIFESTS += [
+ "components.conf",
+]
+
+EXPORTS.mozilla.dom += ["WakeLock.h", "WakeLockJS.h", "WakeLockSentinel.h"]
+
+EXPORTS.mozilla.dom.power += [
+ "PowerManagerService.h",
+]
+
+UNIFIED_SOURCES += [
+ "PowerManagerService.cpp",
+ "WakeLock.cpp",
+ "WakeLockJS.cpp",
+ "WakeLockSentinel.cpp",
+]
+
+MOCHITEST_MANIFESTS += ["tests/mochitest.toml"]
+
+include("/ipc/chromium/chromium-config.mozbuild")
+
+FINAL_LIBRARY = "xul"
diff --git a/dom/power/nsIDOMWakeLockListener.idl b/dom/power/nsIDOMWakeLockListener.idl
new file mode 100644
index 0000000000..366e9cf594
--- /dev/null
+++ b/dom/power/nsIDOMWakeLockListener.idl
@@ -0,0 +1,29 @@
+/* -*- Mode: C++; tab-width: 40; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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 "nsISupports.idl"
+
+[scriptable, function, uuid(4e258af8-cffb-47bc-b16d-e8241243426e)]
+interface nsIDOMMozWakeLockListener : nsISupports
+{
+ /**
+ * The callback will be called when a lock topic changes its lock
+ * state.
+ *
+ * Possible states are:
+ *
+ * - "unlocked" - nobody holds the wake lock.
+ *
+ * - "locked-foreground" - at least one window holds the wake lock,
+ * and it is visible.
+ *
+ * - "locked-background" - at least one window holds the wake lock,
+ * but all of them are hidden.
+ *
+ * @param aTopic The resource name related to the wake lock.
+ * @param aState The wake lock state
+ */
+ void callback(in AString aTopic, in AString aState);
+};
diff --git a/dom/power/nsIPowerManagerService.idl b/dom/power/nsIPowerManagerService.idl
new file mode 100644
index 0000000000..3fef3de9a3
--- /dev/null
+++ b/dom/power/nsIPowerManagerService.idl
@@ -0,0 +1,32 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* 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 "nsISupports.idl"
+
+%{C++
+#define POWERMANAGERSERVICE_CONTRACTID "@mozilla.org/power/powermanagerservice;1"
+%}
+
+interface nsIDOMMozWakeLockListener;
+interface mozIDOMWindow;
+interface nsIWakeLock;
+
+/**
+ * For use with non-content code.
+ */
+[scriptable, builtinclass, uuid(ba7ca4c1-9d92-4425-a83b-85dd7fa953f7)]
+interface nsIPowerManagerService : nsISupports
+{
+ void addWakeLockListener(in nsIDOMMozWakeLockListener aListener);
+ void removeWakeLockListener(in nsIDOMMozWakeLockListener aListener);
+ AString getWakeLockState(in AString aTopic);
+
+ /**
+ * Return a wake lock (MozWakeLock) object of aTopic associated with aWindow.
+ * A wake lock without associated window, e.g. used in chrome, is
+ * always considered invisible.
+ */
+ nsIWakeLock newWakeLock(in AString aTopic, [optional] in mozIDOMWindow aWindow);
+};
diff --git a/dom/power/nsIWakeLock.idl b/dom/power/nsIWakeLock.idl
new file mode 100644
index 0000000000..5ebc158109
--- /dev/null
+++ b/dom/power/nsIWakeLock.idl
@@ -0,0 +1,12 @@
+/* -*- Mode: C++; tab-width: 40; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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 "nsISupports.idl"
+
+[scriptable, builtinclass, uuid(e27e57ce-fa63-4035-b9ef-27c5dc0cc3ae)]
+interface nsIWakeLock : nsISupports
+{
+ void unlock();
+};
diff --git a/dom/power/tests/mochitest.toml b/dom/power/tests/mochitest.toml
new file mode 100644
index 0000000000..0b57119a8a
--- /dev/null
+++ b/dom/power/tests/mochitest.toml
@@ -0,0 +1,9 @@
+[DEFAULT]
+prefs = ["dom.screenwakelock.enabled=true"]
+scheme = "https"
+
+["test_dynamic_pref_change.html"]
+fail-if = ["xorigin"] # cross-origin use requires permissions policy
+
+["test_wakelock_default_permission.html"]
+fail-if = ["xorigin"] # cross-origin use requires permissions policy
diff --git a/dom/power/tests/test_dynamic_pref_change.html b/dom/power/tests/test_dynamic_pref_change.html
new file mode 100644
index 0000000000..c6aba76246
--- /dev/null
+++ b/dom/power/tests/test_dynamic_pref_change.html
@@ -0,0 +1,34 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Test Screen Wake Lock responds to dynamic pref changes</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" href="/tests/SimpleTest/test.css"/>
+ <script type="application/javascript">
+ /* globals SpecialPowers */
+ 'use strict';
+
+ function spinEventLoop() {
+ return new Promise(resolve => {
+ setTimeout(resolve, 0);
+ });
+ }
+
+ add_task(async function my_test() {
+ const lock = await navigator.wakeLock.request("screen");
+ await SpecialPowers.pushPrefEnv({
+ set: [["dom.screenwakelock.enabled", false]],
+ });
+ await spinEventLoop();
+ ok(lock.released, "Lock was released once pref was deactivated");
+ await SpecialPowers.popPrefEnv();
+ });
+ </script>
+</head>
+<body>
+<p id="display"></p>
+<div id="content" style="display: none"></div>
+<pre id="test"></pre>
+</body>
+</html>
diff --git a/dom/power/tests/test_wakelock_default_permission.html b/dom/power/tests/test_wakelock_default_permission.html
new file mode 100644
index 0000000000..78065f6eeb
--- /dev/null
+++ b/dom/power/tests/test_wakelock_default_permission.html
@@ -0,0 +1,20 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Test Screen Wake Lock Permission Granted by Default</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" href="/tests/SimpleTest/test.css"/>
+ <script>
+ add_task(async function my_test() {
+ const status = await navigator.permissions.query({name:'screen-wake-lock'});
+ is(status.state, "granted", "Permission is granted by default");
+ });
+ </script>
+</head>
+<body>
+<p id="display"></p>
+<div id="content" style="display: none"></div>
+<pre id="test"></pre>
+</body>
+</html>