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/. */
7 #include "mozilla/Hal.h"
8 #include "mozilla/HalSensor.h"
10 #include "nsContentUtils.h"
11 #include "nsDeviceSensors.h"
13 #include "nsGlobalWindowInner.h"
14 #include "nsPIDOMWindow.h"
15 #include "nsIScriptObjectPrincipal.h"
16 #include "mozilla/Preferences.h"
17 #include "mozilla/StaticPrefs_device.h"
18 #include "mozilla/Attributes.h"
19 #include "mozilla/dom/BrowsingContext.h"
20 #include "mozilla/dom/DeviceLightEvent.h"
21 #include "mozilla/dom/DeviceOrientationEvent.h"
22 #include "mozilla/dom/Document.h"
23 #include "mozilla/dom/Event.h"
24 #include "mozilla/dom/UserProximityEvent.h"
25 #include "mozilla/ErrorResult.h"
29 using namespace mozilla
;
30 using namespace mozilla::dom
;
37 #define DEFAULT_SENSOR_POLL 100
39 static const nsTArray
<nsIDOMWindow
*>::index_type NoIndex
=
40 nsTArray
<nsIDOMWindow
*>::NoIndex
;
42 class nsDeviceSensorData final
: public nsIDeviceSensorData
{
45 NS_DECL_NSIDEVICESENSORDATA
47 nsDeviceSensorData(unsigned long type
, double x
, double y
, double z
);
50 ~nsDeviceSensorData();
57 nsDeviceSensorData::nsDeviceSensorData(unsigned long type
, double x
, double y
,
59 : mType(type
), mX(x
), mY(y
), mZ(z
) {}
61 NS_INTERFACE_MAP_BEGIN(nsDeviceSensorData
)
62 NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports
, nsIDeviceSensorData
)
65 NS_IMPL_ADDREF(nsDeviceSensorData
)
66 NS_IMPL_RELEASE(nsDeviceSensorData
)
68 nsDeviceSensorData::~nsDeviceSensorData() = default;
70 NS_IMETHODIMP
nsDeviceSensorData::GetType(uint32_t* aType
) {
71 NS_ENSURE_ARG_POINTER(aType
);
76 NS_IMETHODIMP
nsDeviceSensorData::GetX(double* aX
) {
77 NS_ENSURE_ARG_POINTER(aX
);
82 NS_IMETHODIMP
nsDeviceSensorData::GetY(double* aY
) {
83 NS_ENSURE_ARG_POINTER(aY
);
88 NS_IMETHODIMP
nsDeviceSensorData::GetZ(double* aZ
) {
89 NS_ENSURE_ARG_POINTER(aZ
);
94 NS_IMPL_ISUPPORTS(nsDeviceSensors
, nsIDeviceSensors
)
96 nsDeviceSensors::nsDeviceSensors() {
97 mIsUserProximityNear
= false;
98 mLastDOMMotionEventTime
= TimeStamp::Now();
100 for (int i
= 0; i
< NUM_SENSOR_TYPE
; i
++) {
101 nsTArray
<nsIDOMWindow
*>* windows
= new nsTArray
<nsIDOMWindow
*>();
102 mWindowListeners
.AppendElement(windows
);
105 mLastDOMMotionEventTime
= TimeStamp::Now();
108 nsDeviceSensors::~nsDeviceSensors() {
109 for (int i
= 0; i
< NUM_SENSOR_TYPE
; i
++) {
110 if (IsSensorEnabled(i
)) UnregisterSensorObserver((SensorType
)i
, this);
113 for (int i
= 0; i
< NUM_SENSOR_TYPE
; i
++) {
114 delete mWindowListeners
[i
];
118 NS_IMETHODIMP
nsDeviceSensors::HasWindowListener(uint32_t aType
,
119 nsIDOMWindow
* aWindow
,
121 if (!IsSensorAllowedByPref(aType
, aWindow
))
124 *aRetVal
= mWindowListeners
[aType
]->IndexOf(aWindow
) != NoIndex
;
129 class DeviceSensorTestEvent
: public Runnable
{
131 DeviceSensorTestEvent(nsDeviceSensors
* aTarget
, uint32_t aType
)
132 : mozilla::Runnable("DeviceSensorTestEvent"),
136 NS_IMETHOD
Run() override
{
137 SensorData sensorData
;
138 sensorData
.sensor() = static_cast<SensorType
>(mType
);
139 sensorData
.timestamp() = PR_Now();
140 sensorData
.values().AppendElement(0.5f
);
141 sensorData
.values().AppendElement(0.5f
);
142 sensorData
.values().AppendElement(0.5f
);
143 sensorData
.values().AppendElement(0.5f
);
144 mTarget
->Notify(sensorData
);
149 RefPtr
<nsDeviceSensors
> mTarget
;
153 NS_IMETHODIMP
nsDeviceSensors::AddWindowListener(uint32_t aType
,
154 nsIDOMWindow
* aWindow
) {
155 if (!IsSensorAllowedByPref(aType
, aWindow
)) return NS_OK
;
157 if (mWindowListeners
[aType
]->IndexOf(aWindow
) != NoIndex
) return NS_OK
;
159 if (!IsSensorEnabled(aType
)) {
160 RegisterSensorObserver((SensorType
)aType
, this);
163 mWindowListeners
[aType
]->AppendElement(aWindow
);
165 if (StaticPrefs::device_sensors_test_events()) {
166 nsCOMPtr
<nsIRunnable
> event
= new DeviceSensorTestEvent(this, aType
);
167 NS_DispatchToCurrentThread(event
);
173 NS_IMETHODIMP
nsDeviceSensors::RemoveWindowListener(uint32_t aType
,
174 nsIDOMWindow
* aWindow
) {
175 if (mWindowListeners
[aType
]->IndexOf(aWindow
) == NoIndex
) return NS_OK
;
177 mWindowListeners
[aType
]->RemoveElement(aWindow
);
179 if (mWindowListeners
[aType
]->Length() == 0)
180 UnregisterSensorObserver((SensorType
)aType
, this);
185 NS_IMETHODIMP
nsDeviceSensors::RemoveWindowAsListener(nsIDOMWindow
* aWindow
) {
186 for (int i
= 0; i
< NUM_SENSOR_TYPE
; i
++) {
187 RemoveWindowListener((SensorType
)i
, aWindow
);
192 static bool WindowCannotReceiveSensorEvent(nsPIDOMWindowInner
* aWindow
) {
193 // Check to see if this window is in the background.
194 if (!aWindow
|| !aWindow
->IsCurrentInnerWindow()) {
198 nsPIDOMWindowOuter
* windowOuter
= aWindow
->GetOuterWindow();
199 BrowsingContext
* topBC
= aWindow
->GetBrowsingContext()->Top();
200 if (windowOuter
->IsBackground() || !topBC
->GetIsActiveBrowserWindow()) {
204 // Check to see if this window is a cross-origin iframe:
205 if (!topBC
->IsInProcess()) {
209 nsCOMPtr
<nsIScriptObjectPrincipal
> sop
= do_QueryInterface(aWindow
);
210 nsCOMPtr
<nsIScriptObjectPrincipal
> topSop
=
211 do_QueryInterface(topBC
->GetDOMWindow());
212 if (!sop
|| !topSop
) {
216 nsIPrincipal
* principal
= sop
->GetPrincipal();
217 nsIPrincipal
* topPrincipal
= topSop
->GetPrincipal();
218 if (!principal
|| !topPrincipal
) {
222 return !principal
->Subsumes(topPrincipal
);
225 // Holds the device orientation in Euler angle degrees (azimuth, pitch, roll).
227 enum OrientationReference
{ kRelative
= 0, kAbsolute
};
229 static Orientation
RadToDeg(const Orientation
& aOrient
) {
230 const static double kRadToDeg
= 180.0 / M_PI
;
231 return {aOrient
.alpha
* kRadToDeg
, aOrient
.beta
* kRadToDeg
,
232 aOrient
.gamma
* kRadToDeg
};
240 static Orientation
RotationVectorToOrientation(double aX
, double aY
, double aZ
,
244 mat
[0] = 1 - 2 * aY
* aY
- 2 * aZ
* aZ
;
245 mat
[1] = 2 * aX
* aY
- 2 * aZ
* aW
;
246 mat
[2] = 2 * aX
* aZ
+ 2 * aY
* aW
;
248 mat
[3] = 2 * aX
* aY
+ 2 * aZ
* aW
;
249 mat
[4] = 1 - 2 * aX
* aX
- 2 * aZ
* aZ
;
250 mat
[5] = 2 * aY
* aZ
- 2 * aX
* aW
;
252 mat
[6] = 2 * aX
* aZ
- 2 * aY
* aW
;
253 mat
[7] = 2 * aY
* aZ
+ 2 * aX
* aW
;
254 mat
[8] = 1 - 2 * aX
* aX
- 2 * aY
* aY
;
259 orient
.alpha
= atan2(-mat
[1], mat
[4]);
260 orient
.beta
= asin(mat
[7]);
261 orient
.gamma
= atan2(-mat
[6], mat
[8]);
262 } else if (mat
[8] < 0) {
263 orient
.alpha
= atan2(mat
[1], -mat
[4]);
264 orient
.beta
= -asin(mat
[7]);
265 orient
.beta
+= (orient
.beta
>= 0) ? -M_PI
: M_PI
;
266 orient
.gamma
= atan2(mat
[6], -mat
[8]);
269 orient
.alpha
= atan2(-mat
[1], mat
[4]);
270 orient
.beta
= asin(mat
[7]);
271 orient
.gamma
= -M_PI_2
;
272 } else if (mat
[6] < 0) {
273 orient
.alpha
= atan2(mat
[1], -mat
[4]);
274 orient
.beta
= -asin(mat
[7]);
275 orient
.beta
+= (orient
.beta
>= 0) ? -M_PI
: M_PI
;
276 orient
.gamma
= -M_PI_2
;
278 orient
.alpha
= atan2(mat
[3], mat
[0]);
279 orient
.beta
= (mat
[7] > 0) ? M_PI_2
: -M_PI_2
;
284 if (orient
.alpha
< 0) {
285 orient
.alpha
+= 2 * M_PI
;
288 return Orientation::RadToDeg(orient
);
291 void nsDeviceSensors::Notify(const mozilla::hal::SensorData
& aSensorData
) {
292 uint32_t type
= aSensorData
.sensor();
294 const nsTArray
<float>& values
= aSensorData
.values();
295 size_t len
= values
.Length();
296 double x
= len
> 0 ? values
[0] : 0.0;
297 double y
= len
> 1 ? values
[1] : 0.0;
298 double z
= len
> 2 ? values
[2] : 0.0;
299 double w
= len
> 3 ? values
[3] : 0.0;
300 PRTime timestamp
= aSensorData
.timestamp();
302 nsCOMArray
<nsIDOMWindow
> windowListeners
;
303 for (uint32_t i
= 0; i
< mWindowListeners
[type
]->Length(); i
++) {
304 windowListeners
.AppendObject(mWindowListeners
[type
]->SafeElementAt(i
));
307 for (uint32_t i
= windowListeners
.Count(); i
> 0;) {
310 nsCOMPtr
<nsPIDOMWindowInner
> pwindow
=
311 do_QueryInterface(windowListeners
[i
]);
312 if (WindowCannotReceiveSensorEvent(pwindow
)) {
316 if (nsCOMPtr
<Document
> doc
= pwindow
->GetDoc()) {
317 nsCOMPtr
<mozilla::dom::EventTarget
> target
=
318 do_QueryInterface(windowListeners
[i
]);
319 if (type
== nsIDeviceSensorData::TYPE_ACCELERATION
||
320 type
== nsIDeviceSensorData::TYPE_LINEAR_ACCELERATION
||
321 type
== nsIDeviceSensorData::TYPE_GYROSCOPE
) {
322 FireDOMMotionEvent(doc
, target
, type
, timestamp
, x
, y
, z
);
323 } else if (type
== nsIDeviceSensorData::TYPE_ORIENTATION
) {
324 FireDOMOrientationEvent(target
, x
, y
, z
, Orientation::kAbsolute
);
325 } else if (type
== nsIDeviceSensorData::TYPE_ROTATION_VECTOR
) {
326 const Orientation orient
= RotationVectorToOrientation(x
, y
, z
, w
);
327 FireDOMOrientationEvent(target
, orient
.alpha
, orient
.beta
, orient
.gamma
,
328 Orientation::kAbsolute
);
329 } else if (type
== nsIDeviceSensorData::TYPE_GAME_ROTATION_VECTOR
) {
330 const Orientation orient
= RotationVectorToOrientation(x
, y
, z
, w
);
331 FireDOMOrientationEvent(target
, orient
.alpha
, orient
.beta
, orient
.gamma
,
332 Orientation::kRelative
);
333 } else if (type
== nsIDeviceSensorData::TYPE_PROXIMITY
) {
334 MaybeFireDOMUserProximityEvent(target
, x
, z
);
335 } else if (type
== nsIDeviceSensorData::TYPE_LIGHT
) {
336 FireDOMLightEvent(target
, x
);
342 void nsDeviceSensors::FireDOMLightEvent(mozilla::dom::EventTarget
* aTarget
,
344 DeviceLightEventInit init
;
345 init
.mBubbles
= true;
346 init
.mCancelable
= false;
347 init
.mValue
= round(aValue
);
348 RefPtr
<DeviceLightEvent
> event
=
349 DeviceLightEvent::Constructor(aTarget
, u
"devicelight"_ns
, init
);
351 event
->SetTrusted(true);
353 aTarget
->DispatchEvent(*event
);
356 void nsDeviceSensors::MaybeFireDOMUserProximityEvent(
357 mozilla::dom::EventTarget
* aTarget
, double aValue
, double aMax
) {
358 bool near
= (aValue
< aMax
);
359 if (mIsUserProximityNear
!= near
) {
360 mIsUserProximityNear
= near
;
361 FireDOMUserProximityEvent(aTarget
, mIsUserProximityNear
);
365 void nsDeviceSensors::FireDOMUserProximityEvent(
366 mozilla::dom::EventTarget
* aTarget
, bool aNear
) {
367 UserProximityEventInit init
;
368 init
.mBubbles
= true;
369 init
.mCancelable
= false;
371 RefPtr
<UserProximityEvent
> event
=
372 UserProximityEvent::Constructor(aTarget
, u
"userproximity"_ns
, init
);
374 event
->SetTrusted(true);
376 aTarget
->DispatchEvent(*event
);
379 void nsDeviceSensors::FireDOMOrientationEvent(EventTarget
* aTarget
,
380 double aAlpha
, double aBeta
,
381 double aGamma
, bool aIsAbsolute
) {
382 DeviceOrientationEventInit init
;
383 init
.mBubbles
= true;
384 init
.mCancelable
= false;
385 init
.mAlpha
.SetValue(aAlpha
);
386 init
.mBeta
.SetValue(aBeta
);
387 init
.mGamma
.SetValue(aGamma
);
388 init
.mAbsolute
= aIsAbsolute
;
390 auto Dispatch
= [&](EventTarget
* aEventTarget
, const nsAString
& aType
) {
391 RefPtr
<DeviceOrientationEvent
> event
=
392 DeviceOrientationEvent::Constructor(aEventTarget
, aType
, init
);
393 event
->SetTrusted(true);
394 aEventTarget
->DispatchEvent(*event
);
397 Dispatch(aTarget
, aIsAbsolute
? u
"deviceorientationabsolute"_ns
398 : u
"deviceorientation"_ns
);
400 // This is used to determine whether relative events have been dispatched
401 // during the current session, in which case we don't dispatch the additional
402 // compatibility events.
403 static bool sIsDispatchingRelativeEvents
= false;
404 sIsDispatchingRelativeEvents
= sIsDispatchingRelativeEvents
|| !aIsAbsolute
;
406 // Android devices with SENSOR_GAME_ROTATION_VECTOR support dispatch
407 // relative events for "deviceorientation" by default, while other platforms
408 // and devices without such support dispatch absolute events by default.
409 if (aIsAbsolute
&& !sIsDispatchingRelativeEvents
) {
410 // For absolute events on devices without support for relative events,
411 // we need to additionally dispatch type "deviceorientation" to keep
412 // backwards-compatibility.
413 Dispatch(aTarget
, u
"deviceorientation"_ns
);
417 void nsDeviceSensors::FireDOMMotionEvent(Document
* doc
, EventTarget
* target
,
418 uint32_t type
, PRTime timestamp
,
419 double x
, double y
, double z
) {
420 // Attempt to coalesce events
421 TimeDuration sensorPollDuration
=
422 TimeDuration::FromMilliseconds(DEFAULT_SENSOR_POLL
);
424 (TimeStamp::Now() > mLastDOMMotionEventTime
+ sensorPollDuration
) ||
425 StaticPrefs::device_sensors_test_events();
428 case nsIDeviceSensorData::TYPE_LINEAR_ACCELERATION
:
429 if (!mLastAcceleration
) {
430 mLastAcceleration
.emplace();
432 mLastAcceleration
->mX
.SetValue(x
);
433 mLastAcceleration
->mY
.SetValue(y
);
434 mLastAcceleration
->mZ
.SetValue(z
);
436 case nsIDeviceSensorData::TYPE_ACCELERATION
:
437 if (!mLastAccelerationIncludingGravity
) {
438 mLastAccelerationIncludingGravity
.emplace();
440 mLastAccelerationIncludingGravity
->mX
.SetValue(x
);
441 mLastAccelerationIncludingGravity
->mY
.SetValue(y
);
442 mLastAccelerationIncludingGravity
->mZ
.SetValue(z
);
444 case nsIDeviceSensorData::TYPE_GYROSCOPE
:
445 if (!mLastRotationRate
) {
446 mLastRotationRate
.emplace();
448 mLastRotationRate
->mAlpha
.SetValue(x
);
449 mLastRotationRate
->mBeta
.SetValue(y
);
450 mLastRotationRate
->mGamma
.SetValue(z
);
455 if (!mLastAcceleration
) {
456 mLastAcceleration
.emplace();
458 if (!mLastAccelerationIncludingGravity
) {
459 mLastAccelerationIncludingGravity
.emplace();
461 if (!mLastRotationRate
) {
462 mLastRotationRate
.emplace();
464 } else if (!mLastAcceleration
|| !mLastAccelerationIncludingGravity
||
465 !mLastRotationRate
) {
469 IgnoredErrorResult ignored
;
470 RefPtr
<Event
> event
=
471 doc
->CreateEvent(u
"DeviceMotionEvent"_ns
, CallerType::System
, ignored
);
476 DeviceMotionEvent
* me
= static_cast<DeviceMotionEvent
*>(event
.get());
478 me
->InitDeviceMotionEvent(
479 u
"devicemotion"_ns
, true, false, *mLastAcceleration
,
480 *mLastAccelerationIncludingGravity
, *mLastRotationRate
,
481 Nullable
<double>(DEFAULT_SENSOR_POLL
), Nullable
<uint64_t>(timestamp
));
483 event
->SetTrusted(true);
485 target
->DispatchEvent(*event
);
487 mLastRotationRate
.reset();
488 mLastAccelerationIncludingGravity
.reset();
489 mLastAcceleration
.reset();
490 mLastDOMMotionEventTime
= TimeStamp::Now();
493 bool nsDeviceSensors::IsSensorAllowedByPref(uint32_t aType
,
494 nsIDOMWindow
* aWindow
) {
495 // checks "device.sensors.enabled" master pref
496 if (!StaticPrefs::device_sensors_enabled()) {
500 nsCOMPtr
<nsPIDOMWindowInner
> window
= do_QueryInterface(aWindow
);
501 nsCOMPtr
<Document
> doc
;
503 doc
= window
->GetExtantDoc();
507 case nsIDeviceSensorData::TYPE_LINEAR_ACCELERATION
:
508 case nsIDeviceSensorData::TYPE_ACCELERATION
:
509 case nsIDeviceSensorData::TYPE_GYROSCOPE
:
510 // checks "device.sensors.motion.enabled" pref
511 if (!StaticPrefs::device_sensors_motion_enabled()) {
515 doc
->WarnOnceAbout(DeprecatedOperations::eMotionEvent
);
518 case nsIDeviceSensorData::TYPE_GAME_ROTATION_VECTOR
:
519 case nsIDeviceSensorData::TYPE_ORIENTATION
:
520 case nsIDeviceSensorData::TYPE_ROTATION_VECTOR
:
521 // checks "device.sensors.orientation.enabled" pref
522 if (!StaticPrefs::device_sensors_orientation_enabled()) {
526 doc
->WarnOnceAbout(DeprecatedOperations::eOrientationEvent
);
529 case nsIDeviceSensorData::TYPE_PROXIMITY
:
530 // checks "device.sensors.proximity.enabled" pref
531 if (!StaticPrefs::device_sensors_proximity_enabled()) {
535 doc
->WarnOnceAbout(DeprecatedOperations::eProximityEvent
, true);
538 case nsIDeviceSensorData::TYPE_LIGHT
:
539 // checks "device.sensors.ambientLight.enabled" pref
540 if (!StaticPrefs::device_sensors_ambientLight_enabled()) {
544 doc
->WarnOnceAbout(DeprecatedOperations::eAmbientLightEvent
, true);
548 MOZ_ASSERT_UNREACHABLE("Device sensor type not recognised");
555 return !nsGlobalWindowInner::Cast(window
)->ShouldResistFingerprinting();