1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
5  * You can obtain one at http://mozilla.org/MPL/2.0/. */
6 
7 #include "WakeLock.h"
8 #include "mozilla/dom/ContentParent.h"
9 #include "mozilla/dom/Event.h"  // for Event
10 #include "mozilla/Hal.h"
11 #include "mozilla/HalWakeLock.h"
12 #include "nsError.h"
13 #include "mozilla/dom/Document.h"
14 #include "nsPIDOMWindow.h"
15 #include "nsIPropertyBag2.h"
16 
17 using namespace mozilla::hal;
18 
19 namespace mozilla::dom {
20 
21 NS_INTERFACE_MAP_BEGIN(WakeLock)
NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports,nsIDOMEventListener)22   NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIDOMEventListener)
23   NS_INTERFACE_MAP_ENTRY(nsIDOMEventListener)
24   NS_INTERFACE_MAP_ENTRY(nsIObserver)
25   NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference)
26   NS_INTERFACE_MAP_ENTRY(nsIWakeLock)
27 NS_INTERFACE_MAP_END
28 
29 NS_IMPL_ADDREF(WakeLock)
30 NS_IMPL_RELEASE(WakeLock)
31 
32 WakeLock::WakeLock()
33     : mLocked(false),
34       mHidden(true),
35       mContentParentID(CONTENT_PROCESS_ID_UNKNOWN) {}
36 
~WakeLock()37 WakeLock::~WakeLock() {
38   DoUnlock();
39   DetachEventListener();
40 }
41 
Init(const nsAString & aTopic,nsPIDOMWindowInner * aWindow)42 nsresult WakeLock::Init(const nsAString& aTopic, nsPIDOMWindowInner* aWindow) {
43   // Don't Init() a WakeLock twice.
44   MOZ_ASSERT(mTopic.IsEmpty());
45 
46   if (aTopic.IsEmpty()) {
47     return NS_ERROR_INVALID_ARG;
48   }
49 
50   mTopic.Assign(aTopic);
51 
52   mWindow = do_GetWeakReference(aWindow);
53 
54   /**
55    * Null windows are allowed. A wake lock without associated window
56    * is always considered invisible.
57    */
58   if (aWindow) {
59     nsCOMPtr<Document> doc = aWindow->GetExtantDoc();
60     NS_ENSURE_STATE(doc);
61     mHidden = IsDocumentInvisible(*doc);
62   }
63 
64   AttachEventListener();
65   DoLock();
66 
67   return NS_OK;
68 }
69 
70 NS_IMETHODIMP
Observe(nsISupports * aSubject,const char * aTopic,const char16_t * data)71 WakeLock::Observe(nsISupports* aSubject, const char* aTopic,
72                   const char16_t* data) {
73   // If this wake lock was acquired on behalf of another process, unlock it
74   // when that process dies.
75   //
76   // Note that we do /not/ call DoUnlock() here!  The wake lock back-end is
77   // already listening for ipc:content-shutdown messages and will clear out its
78   // tally for the process when it dies.  All we need to do here is ensure that
79   // unlock() becomes a nop.
80 
81   MOZ_ASSERT(!strcmp(aTopic, "ipc:content-shutdown"));
82 
83   nsCOMPtr<nsIPropertyBag2> props = do_QueryInterface(aSubject);
84   if (!props) {
85     NS_WARNING("ipc:content-shutdown message without property bag as subject");
86     return NS_OK;
87   }
88 
89   uint64_t childID = 0;
90   nsresult rv = props->GetPropertyAsUint64(u"childID"_ns, &childID);
91   if (NS_SUCCEEDED(rv)) {
92     if (childID == mContentParentID) {
93       mLocked = false;
94     }
95   } else {
96     NS_WARNING("ipc:content-shutdown message without childID property");
97   }
98   return NS_OK;
99 }
100 
DoLock()101 void WakeLock::DoLock() {
102   if (!mLocked) {
103     // Change the flag immediately to prevent recursive reentering
104     mLocked = true;
105 
106     hal::ModifyWakeLock(
107         mTopic, hal::WAKE_LOCK_ADD_ONE,
108         mHidden ? hal::WAKE_LOCK_ADD_ONE : hal::WAKE_LOCK_NO_CHANGE,
109         mContentParentID);
110   }
111 }
112 
DoUnlock()113 void WakeLock::DoUnlock() {
114   if (mLocked) {
115     // Change the flag immediately to prevent recursive reentering
116     mLocked = false;
117 
118     hal::ModifyWakeLock(
119         mTopic, hal::WAKE_LOCK_REMOVE_ONE,
120         mHidden ? hal::WAKE_LOCK_REMOVE_ONE : hal::WAKE_LOCK_NO_CHANGE,
121         mContentParentID);
122   }
123 }
124 
AttachEventListener()125 void WakeLock::AttachEventListener() {
126   if (nsCOMPtr<nsPIDOMWindowInner> window = do_QueryReferent(mWindow)) {
127     nsCOMPtr<Document> doc = window->GetExtantDoc();
128     if (doc) {
129       doc->AddSystemEventListener(u"visibilitychange"_ns, this,
130                                   /* useCapture = */ true,
131                                   /* wantsUntrusted = */ false);
132 
133       nsCOMPtr<EventTarget> target = do_QueryInterface(window);
134       target->AddSystemEventListener(u"pagehide"_ns, this,
135                                      /* useCapture = */ true,
136                                      /* wantsUntrusted = */ false);
137       target->AddSystemEventListener(u"pageshow"_ns, this,
138                                      /* useCapture = */ true,
139                                      /* wantsUntrusted = */ false);
140     }
141   }
142 }
143 
DetachEventListener()144 void WakeLock::DetachEventListener() {
145   if (nsCOMPtr<nsPIDOMWindowInner> window = do_QueryReferent(mWindow)) {
146     nsCOMPtr<Document> doc = window->GetExtantDoc();
147     if (doc) {
148       doc->RemoveSystemEventListener(u"visibilitychange"_ns, this,
149                                      /* useCapture = */ true);
150       nsCOMPtr<EventTarget> target = do_QueryInterface(window);
151       target->RemoveSystemEventListener(u"pagehide"_ns, this,
152                                         /* useCapture = */ true);
153       target->RemoveSystemEventListener(u"pageshow"_ns, this,
154                                         /* useCapture = */ true);
155     }
156   }
157 }
158 
Unlock(ErrorResult & aRv)159 void WakeLock::Unlock(ErrorResult& aRv) {
160   /*
161    * We throw NS_ERROR_DOM_INVALID_STATE_ERR on double unlock.
162    */
163   if (!mLocked) {
164     aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
165     return;
166   }
167 
168   DoUnlock();
169   DetachEventListener();
170 }
171 
GetTopic(nsAString & aTopic)172 void WakeLock::GetTopic(nsAString& aTopic) { aTopic.Assign(mTopic); }
173 
IsDocumentInvisible(const Document & aDocument) const174 bool WakeLock::IsDocumentInvisible(const Document& aDocument) const {
175   // If document has a child element being used in the picture in picture
176   // mode, which is always visible to users, then we would consider the
177   // document as visible as well.
178   return aDocument.Hidden() && !aDocument.HasPictureInPictureChildElement();
179 }
180 
181 NS_IMETHODIMP
HandleEvent(Event * aEvent)182 WakeLock::HandleEvent(Event* aEvent) {
183   nsAutoString type;
184   aEvent->GetType(type);
185 
186   if (type.EqualsLiteral("visibilitychange")) {
187     nsCOMPtr<Document> doc = do_QueryInterface(aEvent->GetTarget());
188     NS_ENSURE_STATE(doc);
189 
190     bool oldHidden = mHidden;
191     mHidden = IsDocumentInvisible(*doc);
192 
193     if (mLocked && oldHidden != mHidden) {
194       hal::ModifyWakeLock(
195           mTopic, hal::WAKE_LOCK_NO_CHANGE,
196           mHidden ? hal::WAKE_LOCK_ADD_ONE : hal::WAKE_LOCK_REMOVE_ONE,
197           mContentParentID);
198     }
199 
200     return NS_OK;
201   }
202 
203   if (type.EqualsLiteral("pagehide")) {
204     DoUnlock();
205     return NS_OK;
206   }
207 
208   if (type.EqualsLiteral("pageshow")) {
209     DoLock();
210     return NS_OK;
211   }
212 
213   return NS_OK;
214 }
215 
216 NS_IMETHODIMP
Unlock()217 WakeLock::Unlock() {
218   ErrorResult error;
219   Unlock(error);
220   return error.StealNSResult();
221 }
222 
GetParentObject() const223 nsPIDOMWindowInner* WakeLock::GetParentObject() const {
224   nsCOMPtr<nsPIDOMWindowInner> window = do_QueryInterface(mWindow);
225   return window;
226 }
227 
228 }  // namespace mozilla::dom
229