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 nsIDOMEvent::InternalDOMEvent()
10 #include "mozilla/Hal.h"
11 #include "mozilla/HalWakeLock.h"
12 #include "nsError.h"
13 #include "nsIDocument.h"
14 #include "nsIDOMWindow.h"
15 #include "nsIDOMEvent.h"
16 #include "nsPIDOMWindow.h"
17 #include "nsIPropertyBag2.h"
18 
19 using namespace mozilla::hal;
20 
21 namespace mozilla {
22 namespace dom {
23 
24 NS_INTERFACE_MAP_BEGIN(WakeLock)
NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports,nsIDOMEventListener)25   NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIDOMEventListener)
26   NS_INTERFACE_MAP_ENTRY(nsIDOMEventListener)
27   NS_INTERFACE_MAP_ENTRY(nsIObserver)
28   NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference)
29 NS_INTERFACE_MAP_END
30 
31 NS_IMPL_ADDREF(WakeLock)
32 NS_IMPL_RELEASE(WakeLock)
33 
34 WakeLock::WakeLock()
35     : mLocked(false),
36       mHidden(true),
37       mContentParentID(CONTENT_PROCESS_ID_UNKNOWN) {}
38 
~WakeLock()39 WakeLock::~WakeLock() {
40   DoUnlock();
41   DetachEventListener();
42 }
43 
Init(const nsAString & aTopic,nsPIDOMWindowInner * aWindow)44 nsresult WakeLock::Init(const nsAString& aTopic, nsPIDOMWindowInner* aWindow) {
45   // Don't Init() a WakeLock twice.
46   MOZ_ASSERT(mTopic.IsEmpty());
47 
48   if (aTopic.IsEmpty()) {
49     return NS_ERROR_INVALID_ARG;
50   }
51 
52   mTopic.Assign(aTopic);
53 
54   mWindow = do_GetWeakReference(aWindow);
55 
56   /**
57    * Null windows are allowed. A wake lock without associated window
58    * is always considered invisible.
59    */
60   if (aWindow) {
61     nsCOMPtr<nsIDocument> doc = aWindow->GetExtantDoc();
62     NS_ENSURE_STATE(doc);
63     mHidden = doc->Hidden();
64   }
65 
66   AttachEventListener();
67   DoLock();
68 
69   return NS_OK;
70 }
71 
Init(const nsAString & aTopic,ContentParent * aContentParent)72 nsresult WakeLock::Init(const nsAString& aTopic,
73                         ContentParent* aContentParent) {
74   // Don't Init() a WakeLock twice.
75   MOZ_ASSERT(mTopic.IsEmpty());
76   MOZ_ASSERT(aContentParent);
77 
78   if (aTopic.IsEmpty()) {
79     return NS_ERROR_INVALID_ARG;
80   }
81 
82   mTopic.Assign(aTopic);
83   mContentParentID = aContentParent->ChildID();
84   mHidden = false;
85 
86   nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
87   if (obs) {
88     obs->AddObserver(this, "ipc:content-shutdown", /* ownsWeak */ true);
89   }
90 
91   DoLock();
92   return NS_OK;
93 }
94 
95 NS_IMETHODIMP
Observe(nsISupports * aSubject,const char * aTopic,const char16_t * data)96 WakeLock::Observe(nsISupports* aSubject, const char* aTopic,
97                   const char16_t* data) {
98   // If this wake lock was acquired on behalf of another process, unlock it
99   // when that process dies.
100   //
101   // Note that we do /not/ call DoUnlock() here!  The wake lock back-end is
102   // already listening for ipc:content-shutdown messages and will clear out its
103   // tally for the process when it dies.  All we need to do here is ensure that
104   // unlock() becomes a nop.
105 
106   MOZ_ASSERT(!strcmp(aTopic, "ipc:content-shutdown"));
107 
108   nsCOMPtr<nsIPropertyBag2> props = do_QueryInterface(aSubject);
109   if (!props) {
110     NS_WARNING("ipc:content-shutdown message without property bag as subject");
111     return NS_OK;
112   }
113 
114   uint64_t childID = 0;
115   nsresult rv =
116       props->GetPropertyAsUint64(NS_LITERAL_STRING("childID"), &childID);
117   if (NS_SUCCEEDED(rv)) {
118     if (childID == mContentParentID) {
119       mLocked = false;
120     }
121   } else {
122     NS_WARNING("ipc:content-shutdown message without childID property");
123   }
124   return NS_OK;
125 }
126 
DoLock()127 void WakeLock::DoLock() {
128   if (!mLocked) {
129     // Change the flag immediately to prevent recursive reentering
130     mLocked = true;
131 
132     hal::ModifyWakeLock(
133         mTopic, hal::WAKE_LOCK_ADD_ONE,
134         mHidden ? hal::WAKE_LOCK_ADD_ONE : hal::WAKE_LOCK_NO_CHANGE,
135         mContentParentID);
136   }
137 }
138 
DoUnlock()139 void WakeLock::DoUnlock() {
140   if (mLocked) {
141     // Change the flag immediately to prevent recursive reentering
142     mLocked = false;
143 
144     hal::ModifyWakeLock(
145         mTopic, hal::WAKE_LOCK_REMOVE_ONE,
146         mHidden ? hal::WAKE_LOCK_REMOVE_ONE : hal::WAKE_LOCK_NO_CHANGE,
147         mContentParentID);
148   }
149 }
150 
AttachEventListener()151 void WakeLock::AttachEventListener() {
152   if (nsCOMPtr<nsPIDOMWindowInner> window = do_QueryReferent(mWindow)) {
153     nsCOMPtr<nsIDocument> doc = window->GetExtantDoc();
154     if (doc) {
155       doc->AddSystemEventListener(NS_LITERAL_STRING("visibilitychange"), this,
156                                   /* useCapture = */ true,
157                                   /* wantsUntrusted = */ false);
158 
159       nsCOMPtr<EventTarget> target = do_QueryInterface(window);
160       target->AddSystemEventListener(NS_LITERAL_STRING("pagehide"), this,
161                                      /* useCapture = */ true,
162                                      /* wantsUntrusted = */ false);
163       target->AddSystemEventListener(NS_LITERAL_STRING("pageshow"), this,
164                                      /* useCapture = */ true,
165                                      /* wantsUntrusted = */ false);
166     }
167   }
168 }
169 
DetachEventListener()170 void WakeLock::DetachEventListener() {
171   if (nsCOMPtr<nsPIDOMWindowInner> window = do_QueryReferent(mWindow)) {
172     nsCOMPtr<nsIDocument> doc = window->GetExtantDoc();
173     if (doc) {
174       doc->RemoveSystemEventListener(NS_LITERAL_STRING("visibilitychange"),
175                                      this,
176                                      /* useCapture = */ true);
177       nsCOMPtr<EventTarget> target = do_QueryInterface(window);
178       target->RemoveSystemEventListener(NS_LITERAL_STRING("pagehide"), this,
179                                         /* useCapture = */ true);
180       target->RemoveSystemEventListener(NS_LITERAL_STRING("pageshow"), this,
181                                         /* useCapture = */ true);
182     }
183   }
184 }
185 
Unlock(ErrorResult & aRv)186 void WakeLock::Unlock(ErrorResult& aRv) {
187   /*
188    * We throw NS_ERROR_DOM_INVALID_STATE_ERR on double unlock.
189    */
190   if (!mLocked) {
191     aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
192     return;
193   }
194 
195   DoUnlock();
196   DetachEventListener();
197 }
198 
GetTopic(nsAString & aTopic)199 void WakeLock::GetTopic(nsAString& aTopic) { aTopic.Assign(mTopic); }
200 
201 NS_IMETHODIMP
HandleEvent(nsIDOMEvent * aEvent)202 WakeLock::HandleEvent(nsIDOMEvent* aEvent) {
203   nsAutoString type;
204   aEvent->GetType(type);
205 
206   if (type.EqualsLiteral("visibilitychange")) {
207     nsCOMPtr<nsIDocument> doc =
208         do_QueryInterface(aEvent->InternalDOMEvent()->GetTarget());
209     NS_ENSURE_STATE(doc);
210 
211     bool oldHidden = mHidden;
212     mHidden = doc->Hidden();
213 
214     if (mLocked && oldHidden != mHidden) {
215       hal::ModifyWakeLock(
216           mTopic, hal::WAKE_LOCK_NO_CHANGE,
217           mHidden ? hal::WAKE_LOCK_ADD_ONE : hal::WAKE_LOCK_REMOVE_ONE,
218           mContentParentID);
219     }
220 
221     return NS_OK;
222   }
223 
224   if (type.EqualsLiteral("pagehide")) {
225     DoUnlock();
226     return NS_OK;
227   }
228 
229   if (type.EqualsLiteral("pageshow")) {
230     DoLock();
231     return NS_OK;
232   }
233 
234   return NS_OK;
235 }
236 
GetParentObject() const237 nsPIDOMWindowInner* WakeLock::GetParentObject() const {
238   nsCOMPtr<nsPIDOMWindowInner> window = do_QueryInterface(mWindow);
239   return window;
240 }
241 
242 }  // namespace dom
243 }  // namespace mozilla
244