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