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