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
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6
7 #include "mozilla/Hal.h"
8 #include "mozilla/HalSensor.h"
9
10 #include "nsContentUtils.h"
11 #include "nsDeviceSensors.h"
12
13 #include "nsPIDOMWindow.h"
14 #include "nsIScriptObjectPrincipal.h"
15 #include "mozilla/Preferences.h"
16 #include "mozilla/StaticPrefs_device.h"
17 #include "mozilla/Attributes.h"
18 #include "mozilla/dom/BrowsingContext.h"
19 #include "mozilla/dom/DeviceLightEvent.h"
20 #include "mozilla/dom/DeviceOrientationEvent.h"
21 #include "mozilla/dom/Document.h"
22 #include "mozilla/dom/Event.h"
23 #include "mozilla/dom/UserProximityEvent.h"
24 #include "mozilla/ErrorResult.h"
25
26 #include <cmath>
27
28 using namespace mozilla;
29 using namespace mozilla::dom;
30 using namespace hal;
31
32 class nsIDOMWindow;
33
34 #undef near
35
36 #define DEFAULT_SENSOR_POLL 100
37
38 static const nsTArray<nsIDOMWindow*>::index_type NoIndex =
39 nsTArray<nsIDOMWindow*>::NoIndex;
40
41 class nsDeviceSensorData final : public nsIDeviceSensorData {
42 public:
43 NS_DECL_ISUPPORTS
44 NS_DECL_NSIDEVICESENSORDATA
45
46 nsDeviceSensorData(unsigned long type, double x, double y, double z);
47
48 private:
49 ~nsDeviceSensorData();
50
51 protected:
52 unsigned long mType;
53 double mX, mY, mZ;
54 };
55
nsDeviceSensorData(unsigned long type,double x,double y,double z)56 nsDeviceSensorData::nsDeviceSensorData(unsigned long type, double x, double y,
57 double z)
58 : mType(type), mX(x), mY(y), mZ(z) {}
59
60 NS_INTERFACE_MAP_BEGIN(nsDeviceSensorData)
61 NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIDeviceSensorData)
62 NS_INTERFACE_MAP_END
63
64 NS_IMPL_ADDREF(nsDeviceSensorData)
65 NS_IMPL_RELEASE(nsDeviceSensorData)
66
67 nsDeviceSensorData::~nsDeviceSensorData() = default;
68
GetType(uint32_t * aType)69 NS_IMETHODIMP nsDeviceSensorData::GetType(uint32_t* aType) {
70 NS_ENSURE_ARG_POINTER(aType);
71 *aType = mType;
72 return NS_OK;
73 }
74
GetX(double * aX)75 NS_IMETHODIMP nsDeviceSensorData::GetX(double* aX) {
76 NS_ENSURE_ARG_POINTER(aX);
77 *aX = mX;
78 return NS_OK;
79 }
80
GetY(double * aY)81 NS_IMETHODIMP nsDeviceSensorData::GetY(double* aY) {
82 NS_ENSURE_ARG_POINTER(aY);
83 *aY = mY;
84 return NS_OK;
85 }
86
GetZ(double * aZ)87 NS_IMETHODIMP nsDeviceSensorData::GetZ(double* aZ) {
88 NS_ENSURE_ARG_POINTER(aZ);
89 *aZ = mZ;
90 return NS_OK;
91 }
92
NS_IMPL_ISUPPORTS(nsDeviceSensors,nsIDeviceSensors)93 NS_IMPL_ISUPPORTS(nsDeviceSensors, nsIDeviceSensors)
94
95 nsDeviceSensors::nsDeviceSensors() {
96 mIsUserProximityNear = false;
97 mLastDOMMotionEventTime = TimeStamp::Now();
98
99 for (int i = 0; i < NUM_SENSOR_TYPE; i++) {
100 nsTArray<nsIDOMWindow*>* windows = new nsTArray<nsIDOMWindow*>();
101 mWindowListeners.AppendElement(windows);
102 }
103
104 mLastDOMMotionEventTime = TimeStamp::Now();
105 }
106
~nsDeviceSensors()107 nsDeviceSensors::~nsDeviceSensors() {
108 for (int i = 0; i < NUM_SENSOR_TYPE; i++) {
109 if (IsSensorEnabled(i)) UnregisterSensorObserver((SensorType)i, this);
110 }
111
112 for (int i = 0; i < NUM_SENSOR_TYPE; i++) {
113 delete mWindowListeners[i];
114 }
115 }
116
HasWindowListener(uint32_t aType,nsIDOMWindow * aWindow,bool * aRetVal)117 NS_IMETHODIMP nsDeviceSensors::HasWindowListener(uint32_t aType,
118 nsIDOMWindow* aWindow,
119 bool* aRetVal) {
120 if (!IsSensorAllowedByPref(aType, aWindow))
121 *aRetVal = false;
122 else
123 *aRetVal = mWindowListeners[aType]->IndexOf(aWindow) != NoIndex;
124
125 return NS_OK;
126 }
127
128 class DeviceSensorTestEvent : public Runnable {
129 public:
DeviceSensorTestEvent(nsDeviceSensors * aTarget,uint32_t aType)130 DeviceSensorTestEvent(nsDeviceSensors* aTarget, uint32_t aType)
131 : mozilla::Runnable("DeviceSensorTestEvent"),
132 mTarget(aTarget),
133 mType(aType) {}
134
Run()135 NS_IMETHOD Run() override {
136 SensorData sensorData;
137 sensorData.sensor() = static_cast<SensorType>(mType);
138 sensorData.timestamp() = PR_Now();
139 sensorData.values().AppendElement(0.5f);
140 sensorData.values().AppendElement(0.5f);
141 sensorData.values().AppendElement(0.5f);
142 sensorData.values().AppendElement(0.5f);
143 mTarget->Notify(sensorData);
144 return NS_OK;
145 }
146
147 private:
148 RefPtr<nsDeviceSensors> mTarget;
149 uint32_t mType;
150 };
151
AddWindowListener(uint32_t aType,nsIDOMWindow * aWindow)152 NS_IMETHODIMP nsDeviceSensors::AddWindowListener(uint32_t aType,
153 nsIDOMWindow* aWindow) {
154 if (!IsSensorAllowedByPref(aType, aWindow)) return NS_OK;
155
156 if (mWindowListeners[aType]->IndexOf(aWindow) != NoIndex) return NS_OK;
157
158 if (!IsSensorEnabled(aType)) {
159 RegisterSensorObserver((SensorType)aType, this);
160 }
161
162 mWindowListeners[aType]->AppendElement(aWindow);
163
164 if (StaticPrefs::device_sensors_test_events()) {
165 nsCOMPtr<nsIRunnable> event = new DeviceSensorTestEvent(this, aType);
166 NS_DispatchToCurrentThread(event);
167 }
168
169 return NS_OK;
170 }
171
RemoveWindowListener(uint32_t aType,nsIDOMWindow * aWindow)172 NS_IMETHODIMP nsDeviceSensors::RemoveWindowListener(uint32_t aType,
173 nsIDOMWindow* aWindow) {
174 if (mWindowListeners[aType]->IndexOf(aWindow) == NoIndex) return NS_OK;
175
176 mWindowListeners[aType]->RemoveElement(aWindow);
177
178 if (mWindowListeners[aType]->Length() == 0)
179 UnregisterSensorObserver((SensorType)aType, this);
180
181 return NS_OK;
182 }
183
RemoveWindowAsListener(nsIDOMWindow * aWindow)184 NS_IMETHODIMP nsDeviceSensors::RemoveWindowAsListener(nsIDOMWindow* aWindow) {
185 for (int i = 0; i < NUM_SENSOR_TYPE; i++) {
186 RemoveWindowListener((SensorType)i, aWindow);
187 }
188 return NS_OK;
189 }
190
WindowCannotReceiveSensorEvent(nsPIDOMWindowInner * aWindow)191 static bool WindowCannotReceiveSensorEvent(nsPIDOMWindowInner* aWindow) {
192 // Check to see if this window is in the background.
193 if (!aWindow || !aWindow->IsCurrentInnerWindow()) {
194 return true;
195 }
196
197 nsPIDOMWindowOuter* windowOuter = aWindow->GetOuterWindow();
198 BrowsingContext* topBC = aWindow->GetBrowsingContext()->Top();
199 if (windowOuter->IsBackground() || !topBC->GetIsActiveBrowserWindow()) {
200 return true;
201 }
202
203 // Check to see if this window is a cross-origin iframe:
204 if (!topBC->IsInProcess()) {
205 return true;
206 }
207
208 nsCOMPtr<nsIScriptObjectPrincipal> sop = do_QueryInterface(aWindow);
209 nsCOMPtr<nsIScriptObjectPrincipal> topSop =
210 do_QueryInterface(topBC->GetDOMWindow());
211 if (!sop || !topSop) {
212 return true;
213 }
214
215 nsIPrincipal* principal = sop->GetPrincipal();
216 nsIPrincipal* topPrincipal = topSop->GetPrincipal();
217 if (!principal || !topPrincipal) {
218 return true;
219 }
220
221 return !principal->Subsumes(topPrincipal);
222 }
223
224 // Holds the device orientation in Euler angle degrees (azimuth, pitch, roll).
225 struct Orientation {
226 enum OrientationReference { kRelative = 0, kAbsolute };
227
RadToDegOrientation228 static Orientation RadToDeg(const Orientation& aOrient) {
229 const static double kRadToDeg = 180.0 / M_PI;
230 return {aOrient.alpha * kRadToDeg, aOrient.beta * kRadToDeg,
231 aOrient.gamma * kRadToDeg};
232 }
233
234 double alpha;
235 double beta;
236 double gamma;
237 };
238
RotationVectorToOrientation(double aX,double aY,double aZ,double aW)239 static Orientation RotationVectorToOrientation(double aX, double aY, double aZ,
240 double aW) {
241 double mat[9];
242
243 mat[0] = 1 - 2 * aY * aY - 2 * aZ * aZ;
244 mat[1] = 2 * aX * aY - 2 * aZ * aW;
245 mat[2] = 2 * aX * aZ + 2 * aY * aW;
246
247 mat[3] = 2 * aX * aY + 2 * aZ * aW;
248 mat[4] = 1 - 2 * aX * aX - 2 * aZ * aZ;
249 mat[5] = 2 * aY * aZ - 2 * aX * aW;
250
251 mat[6] = 2 * aX * aZ - 2 * aY * aW;
252 mat[7] = 2 * aY * aZ + 2 * aX * aW;
253 mat[8] = 1 - 2 * aX * aX - 2 * aY * aY;
254
255 Orientation orient;
256
257 if (mat[8] > 0) {
258 orient.alpha = atan2(-mat[1], mat[4]);
259 orient.beta = asin(mat[7]);
260 orient.gamma = atan2(-mat[6], mat[8]);
261 } else if (mat[8] < 0) {
262 orient.alpha = atan2(mat[1], -mat[4]);
263 orient.beta = -asin(mat[7]);
264 orient.beta += (orient.beta >= 0) ? -M_PI : M_PI;
265 orient.gamma = atan2(mat[6], -mat[8]);
266 } else {
267 if (mat[6] > 0) {
268 orient.alpha = atan2(-mat[1], mat[4]);
269 orient.beta = asin(mat[7]);
270 orient.gamma = -M_PI_2;
271 } else if (mat[6] < 0) {
272 orient.alpha = atan2(mat[1], -mat[4]);
273 orient.beta = -asin(mat[7]);
274 orient.beta += (orient.beta >= 0) ? -M_PI : M_PI;
275 orient.gamma = -M_PI_2;
276 } else {
277 orient.alpha = atan2(mat[3], mat[0]);
278 orient.beta = (mat[7] > 0) ? M_PI_2 : -M_PI_2;
279 orient.gamma = 0;
280 }
281 }
282
283 if (orient.alpha < 0) {
284 orient.alpha += 2 * M_PI;
285 }
286
287 return Orientation::RadToDeg(orient);
288 }
289
Notify(const mozilla::hal::SensorData & aSensorData)290 void nsDeviceSensors::Notify(const mozilla::hal::SensorData& aSensorData) {
291 uint32_t type = aSensorData.sensor();
292
293 const nsTArray<float>& values = aSensorData.values();
294 size_t len = values.Length();
295 double x = len > 0 ? values[0] : 0.0;
296 double y = len > 1 ? values[1] : 0.0;
297 double z = len > 2 ? values[2] : 0.0;
298 double w = len > 3 ? values[3] : 0.0;
299 PRTime timestamp = aSensorData.timestamp();
300
301 nsCOMArray<nsIDOMWindow> windowListeners;
302 for (uint32_t i = 0; i < mWindowListeners[type]->Length(); i++) {
303 windowListeners.AppendObject(mWindowListeners[type]->SafeElementAt(i));
304 }
305
306 for (uint32_t i = windowListeners.Count(); i > 0;) {
307 --i;
308
309 nsCOMPtr<nsPIDOMWindowInner> pwindow =
310 do_QueryInterface(windowListeners[i]);
311 if (WindowCannotReceiveSensorEvent(pwindow)) {
312 continue;
313 }
314
315 if (nsCOMPtr<Document> doc = pwindow->GetDoc()) {
316 nsCOMPtr<mozilla::dom::EventTarget> target =
317 do_QueryInterface(windowListeners[i]);
318 if (type == nsIDeviceSensorData::TYPE_ACCELERATION ||
319 type == nsIDeviceSensorData::TYPE_LINEAR_ACCELERATION ||
320 type == nsIDeviceSensorData::TYPE_GYROSCOPE) {
321 FireDOMMotionEvent(doc, target, type, timestamp, x, y, z);
322 } else if (type == nsIDeviceSensorData::TYPE_ORIENTATION) {
323 FireDOMOrientationEvent(target, x, y, z, Orientation::kAbsolute);
324 } else if (type == nsIDeviceSensorData::TYPE_ROTATION_VECTOR) {
325 const Orientation orient = RotationVectorToOrientation(x, y, z, w);
326 FireDOMOrientationEvent(target, orient.alpha, orient.beta, orient.gamma,
327 Orientation::kAbsolute);
328 } else if (type == nsIDeviceSensorData::TYPE_GAME_ROTATION_VECTOR) {
329 const Orientation orient = RotationVectorToOrientation(x, y, z, w);
330 FireDOMOrientationEvent(target, orient.alpha, orient.beta, orient.gamma,
331 Orientation::kRelative);
332 } else if (type == nsIDeviceSensorData::TYPE_PROXIMITY) {
333 MaybeFireDOMUserProximityEvent(target, x, z);
334 } else if (type == nsIDeviceSensorData::TYPE_LIGHT) {
335 FireDOMLightEvent(target, x);
336 }
337 }
338 }
339 }
340
FireDOMLightEvent(mozilla::dom::EventTarget * aTarget,double aValue)341 void nsDeviceSensors::FireDOMLightEvent(mozilla::dom::EventTarget* aTarget,
342 double aValue) {
343 DeviceLightEventInit init;
344 init.mBubbles = true;
345 init.mCancelable = false;
346 init.mValue = round(aValue);
347 RefPtr<DeviceLightEvent> event =
348 DeviceLightEvent::Constructor(aTarget, u"devicelight"_ns, init);
349
350 event->SetTrusted(true);
351
352 aTarget->DispatchEvent(*event);
353 }
354
MaybeFireDOMUserProximityEvent(mozilla::dom::EventTarget * aTarget,double aValue,double aMax)355 void nsDeviceSensors::MaybeFireDOMUserProximityEvent(
356 mozilla::dom::EventTarget* aTarget, double aValue, double aMax) {
357 bool near = (aValue < aMax);
358 if (mIsUserProximityNear != near) {
359 mIsUserProximityNear = near;
360 FireDOMUserProximityEvent(aTarget, mIsUserProximityNear);
361 }
362 }
363
FireDOMUserProximityEvent(mozilla::dom::EventTarget * aTarget,bool aNear)364 void nsDeviceSensors::FireDOMUserProximityEvent(
365 mozilla::dom::EventTarget* aTarget, bool aNear) {
366 UserProximityEventInit init;
367 init.mBubbles = true;
368 init.mCancelable = false;
369 init.mNear = aNear;
370 RefPtr<UserProximityEvent> event =
371 UserProximityEvent::Constructor(aTarget, u"userproximity"_ns, init);
372
373 event->SetTrusted(true);
374
375 aTarget->DispatchEvent(*event);
376 }
377
FireDOMOrientationEvent(EventTarget * aTarget,double aAlpha,double aBeta,double aGamma,bool aIsAbsolute)378 void nsDeviceSensors::FireDOMOrientationEvent(EventTarget* aTarget,
379 double aAlpha, double aBeta,
380 double aGamma, bool aIsAbsolute) {
381 DeviceOrientationEventInit init;
382 init.mBubbles = true;
383 init.mCancelable = false;
384 init.mAlpha.SetValue(aAlpha);
385 init.mBeta.SetValue(aBeta);
386 init.mGamma.SetValue(aGamma);
387 init.mAbsolute = aIsAbsolute;
388
389 auto Dispatch = [&](EventTarget* aEventTarget, const nsAString& aType) {
390 RefPtr<DeviceOrientationEvent> event =
391 DeviceOrientationEvent::Constructor(aEventTarget, aType, init);
392 event->SetTrusted(true);
393 aEventTarget->DispatchEvent(*event);
394 };
395
396 Dispatch(aTarget, aIsAbsolute ? u"absolutedeviceorientation"_ns
397 : u"deviceorientation"_ns);
398
399 // This is used to determine whether relative events have been dispatched
400 // during the current session, in which case we don't dispatch the additional
401 // compatibility events.
402 static bool sIsDispatchingRelativeEvents = false;
403 sIsDispatchingRelativeEvents = sIsDispatchingRelativeEvents || !aIsAbsolute;
404
405 // Android devices with SENSOR_GAME_ROTATION_VECTOR support dispatch
406 // relative events for "deviceorientation" by default, while other platforms
407 // and devices without such support dispatch absolute events by default.
408 if (aIsAbsolute && !sIsDispatchingRelativeEvents) {
409 // For absolute events on devices without support for relative events,
410 // we need to additionally dispatch type "deviceorientation" to keep
411 // backwards-compatibility.
412 Dispatch(aTarget, u"deviceorientation"_ns);
413 }
414 }
415
FireDOMMotionEvent(Document * doc,EventTarget * target,uint32_t type,PRTime timestamp,double x,double y,double z)416 void nsDeviceSensors::FireDOMMotionEvent(Document* doc, EventTarget* target,
417 uint32_t type, PRTime timestamp,
418 double x, double y, double z) {
419 // Attempt to coalesce events
420 TimeDuration sensorPollDuration =
421 TimeDuration::FromMilliseconds(DEFAULT_SENSOR_POLL);
422 bool fireEvent =
423 (TimeStamp::Now() > mLastDOMMotionEventTime + sensorPollDuration) ||
424 StaticPrefs::device_sensors_test_events();
425
426 switch (type) {
427 case nsIDeviceSensorData::TYPE_LINEAR_ACCELERATION:
428 if (!mLastAcceleration) {
429 mLastAcceleration.emplace();
430 }
431 mLastAcceleration->mX.SetValue(x);
432 mLastAcceleration->mY.SetValue(y);
433 mLastAcceleration->mZ.SetValue(z);
434 break;
435 case nsIDeviceSensorData::TYPE_ACCELERATION:
436 if (!mLastAccelerationIncludingGravity) {
437 mLastAccelerationIncludingGravity.emplace();
438 }
439 mLastAccelerationIncludingGravity->mX.SetValue(x);
440 mLastAccelerationIncludingGravity->mY.SetValue(y);
441 mLastAccelerationIncludingGravity->mZ.SetValue(z);
442 break;
443 case nsIDeviceSensorData::TYPE_GYROSCOPE:
444 if (!mLastRotationRate) {
445 mLastRotationRate.emplace();
446 }
447 mLastRotationRate->mAlpha.SetValue(x);
448 mLastRotationRate->mBeta.SetValue(y);
449 mLastRotationRate->mGamma.SetValue(z);
450 break;
451 }
452
453 if (fireEvent) {
454 if (!mLastAcceleration) {
455 mLastAcceleration.emplace();
456 }
457 if (!mLastAccelerationIncludingGravity) {
458 mLastAccelerationIncludingGravity.emplace();
459 }
460 if (!mLastRotationRate) {
461 mLastRotationRate.emplace();
462 }
463 } else if (!mLastAcceleration || !mLastAccelerationIncludingGravity ||
464 !mLastRotationRate) {
465 return;
466 }
467
468 IgnoredErrorResult ignored;
469 RefPtr<Event> event =
470 doc->CreateEvent(u"DeviceMotionEvent"_ns, CallerType::System, ignored);
471 if (!event) {
472 return;
473 }
474
475 DeviceMotionEvent* me = static_cast<DeviceMotionEvent*>(event.get());
476
477 me->InitDeviceMotionEvent(
478 u"devicemotion"_ns, true, false, *mLastAcceleration,
479 *mLastAccelerationIncludingGravity, *mLastRotationRate,
480 Nullable<double>(DEFAULT_SENSOR_POLL), Nullable<uint64_t>(timestamp));
481
482 event->SetTrusted(true);
483
484 target->DispatchEvent(*event);
485
486 mLastRotationRate.reset();
487 mLastAccelerationIncludingGravity.reset();
488 mLastAcceleration.reset();
489 mLastDOMMotionEventTime = TimeStamp::Now();
490 }
491
IsSensorAllowedByPref(uint32_t aType,nsIDOMWindow * aWindow)492 bool nsDeviceSensors::IsSensorAllowedByPref(uint32_t aType,
493 nsIDOMWindow* aWindow) {
494 // checks "device.sensors.enabled" master pref
495 if (!StaticPrefs::device_sensors_enabled()) {
496 return false;
497 }
498
499 nsCOMPtr<nsPIDOMWindowInner> window = do_QueryInterface(aWindow);
500 nsCOMPtr<Document> doc;
501 if (window) {
502 doc = window->GetExtantDoc();
503 }
504
505 switch (aType) {
506 case nsIDeviceSensorData::TYPE_LINEAR_ACCELERATION:
507 case nsIDeviceSensorData::TYPE_ACCELERATION:
508 case nsIDeviceSensorData::TYPE_GYROSCOPE:
509 // checks "device.sensors.motion.enabled" pref
510 if (!StaticPrefs::device_sensors_motion_enabled()) {
511 return false;
512 } else if (doc) {
513 doc->WarnOnceAbout(DeprecatedOperations::eMotionEvent);
514 }
515 break;
516 case nsIDeviceSensorData::TYPE_GAME_ROTATION_VECTOR:
517 case nsIDeviceSensorData::TYPE_ORIENTATION:
518 case nsIDeviceSensorData::TYPE_ROTATION_VECTOR:
519 // checks "device.sensors.orientation.enabled" pref
520 if (!StaticPrefs::device_sensors_orientation_enabled()) {
521 return false;
522 } else if (doc) {
523 doc->WarnOnceAbout(DeprecatedOperations::eOrientationEvent);
524 }
525 break;
526 case nsIDeviceSensorData::TYPE_PROXIMITY:
527 // checks "device.sensors.proximity.enabled" pref
528 if (!StaticPrefs::device_sensors_proximity_enabled()) {
529 return false;
530 } else if (doc) {
531 doc->WarnOnceAbout(DeprecatedOperations::eProximityEvent, true);
532 }
533 break;
534 case nsIDeviceSensorData::TYPE_LIGHT:
535 // checks "device.sensors.ambientLight.enabled" pref
536 if (!StaticPrefs::device_sensors_ambientLight_enabled()) {
537 return false;
538 } else if (doc) {
539 doc->WarnOnceAbout(DeprecatedOperations::eAmbientLightEvent, true);
540 }
541 break;
542 default:
543 MOZ_ASSERT_UNREACHABLE("Device sensor type not recognised");
544 return false;
545 }
546
547 if (!window) {
548 return true;
549 }
550
551 nsCOMPtr<nsIScriptObjectPrincipal> soPrincipal = do_QueryInterface(window);
552 return !nsContentUtils::ShouldResistFingerprinting(
553 soPrincipal->GetPrincipal());
554 }
555