Bug 1825333 - Make toolkit/components/sessionstore buildable outside of a unified...
[gecko.git] / dom / system / nsDeviceSensors.cpp
blob5b8250ae7e644837c01772f5624bde07c99fea53
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"
27 #include <cmath>
29 using namespace mozilla;
30 using namespace mozilla::dom;
31 using namespace hal;
33 class nsIDOMWindow;
35 #undef near
37 #define DEFAULT_SENSOR_POLL 100
39 static const nsTArray<nsIDOMWindow*>::index_type NoIndex =
40 nsTArray<nsIDOMWindow*>::NoIndex;
42 class nsDeviceSensorData final : public nsIDeviceSensorData {
43 public:
44 NS_DECL_ISUPPORTS
45 NS_DECL_NSIDEVICESENSORDATA
47 nsDeviceSensorData(unsigned long type, double x, double y, double z);
49 private:
50 ~nsDeviceSensorData();
52 protected:
53 unsigned long mType;
54 double mX, mY, mZ;
57 nsDeviceSensorData::nsDeviceSensorData(unsigned long type, double x, double y,
58 double z)
59 : mType(type), mX(x), mY(y), mZ(z) {}
61 NS_INTERFACE_MAP_BEGIN(nsDeviceSensorData)
62 NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIDeviceSensorData)
63 NS_INTERFACE_MAP_END
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);
72 *aType = mType;
73 return NS_OK;
76 NS_IMETHODIMP nsDeviceSensorData::GetX(double* aX) {
77 NS_ENSURE_ARG_POINTER(aX);
78 *aX = mX;
79 return NS_OK;
82 NS_IMETHODIMP nsDeviceSensorData::GetY(double* aY) {
83 NS_ENSURE_ARG_POINTER(aY);
84 *aY = mY;
85 return NS_OK;
88 NS_IMETHODIMP nsDeviceSensorData::GetZ(double* aZ) {
89 NS_ENSURE_ARG_POINTER(aZ);
90 *aZ = mZ;
91 return NS_OK;
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,
120 bool* aRetVal) {
121 if (!IsSensorAllowedByPref(aType, aWindow))
122 *aRetVal = false;
123 else
124 *aRetVal = mWindowListeners[aType]->IndexOf(aWindow) != NoIndex;
126 return NS_OK;
129 class DeviceSensorTestEvent : public Runnable {
130 public:
131 DeviceSensorTestEvent(nsDeviceSensors* aTarget, uint32_t aType)
132 : mozilla::Runnable("DeviceSensorTestEvent"),
133 mTarget(aTarget),
134 mType(aType) {}
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);
145 return NS_OK;
148 private:
149 RefPtr<nsDeviceSensors> mTarget;
150 uint32_t mType;
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);
170 return NS_OK;
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);
182 return NS_OK;
185 NS_IMETHODIMP nsDeviceSensors::RemoveWindowAsListener(nsIDOMWindow* aWindow) {
186 for (int i = 0; i < NUM_SENSOR_TYPE; i++) {
187 RemoveWindowListener((SensorType)i, aWindow);
189 return NS_OK;
192 static bool WindowCannotReceiveSensorEvent(nsPIDOMWindowInner* aWindow) {
193 // Check to see if this window is in the background.
194 if (!aWindow || !aWindow->IsCurrentInnerWindow()) {
195 return true;
198 nsPIDOMWindowOuter* windowOuter = aWindow->GetOuterWindow();
199 BrowsingContext* topBC = aWindow->GetBrowsingContext()->Top();
200 if (windowOuter->IsBackground() || !topBC->GetIsActiveBrowserWindow()) {
201 return true;
204 // Check to see if this window is a cross-origin iframe:
205 if (!topBC->IsInProcess()) {
206 return true;
209 nsCOMPtr<nsIScriptObjectPrincipal> sop = do_QueryInterface(aWindow);
210 nsCOMPtr<nsIScriptObjectPrincipal> topSop =
211 do_QueryInterface(topBC->GetDOMWindow());
212 if (!sop || !topSop) {
213 return true;
216 nsIPrincipal* principal = sop->GetPrincipal();
217 nsIPrincipal* topPrincipal = topSop->GetPrincipal();
218 if (!principal || !topPrincipal) {
219 return true;
222 return !principal->Subsumes(topPrincipal);
225 // Holds the device orientation in Euler angle degrees (azimuth, pitch, roll).
226 struct Orientation {
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};
235 double alpha;
236 double beta;
237 double gamma;
240 static Orientation RotationVectorToOrientation(double aX, double aY, double aZ,
241 double aW) {
242 double mat[9];
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;
256 Orientation orient;
258 if (mat[8] > 0) {
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]);
267 } else {
268 if (mat[6] > 0) {
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;
277 } else {
278 orient.alpha = atan2(mat[3], mat[0]);
279 orient.beta = (mat[7] > 0) ? M_PI_2 : -M_PI_2;
280 orient.gamma = 0;
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;) {
308 --i;
310 nsCOMPtr<nsPIDOMWindowInner> pwindow =
311 do_QueryInterface(windowListeners[i]);
312 if (WindowCannotReceiveSensorEvent(pwindow)) {
313 continue;
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,
343 double aValue) {
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;
370 init.mNear = aNear;
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);
423 bool fireEvent =
424 (TimeStamp::Now() > mLastDOMMotionEventTime + sensorPollDuration) ||
425 StaticPrefs::device_sensors_test_events();
427 switch (type) {
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);
435 break;
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);
443 break;
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);
451 break;
454 if (fireEvent) {
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) {
466 return;
469 IgnoredErrorResult ignored;
470 RefPtr<Event> event =
471 doc->CreateEvent(u"DeviceMotionEvent"_ns, CallerType::System, ignored);
472 if (!event) {
473 return;
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()) {
497 return false;
500 nsCOMPtr<nsPIDOMWindowInner> window = do_QueryInterface(aWindow);
501 nsCOMPtr<Document> doc;
502 if (window) {
503 doc = window->GetExtantDoc();
506 switch (aType) {
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()) {
512 return false;
514 if (doc) {
515 doc->WarnOnceAbout(DeprecatedOperations::eMotionEvent);
517 break;
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()) {
523 return false;
525 if (doc) {
526 doc->WarnOnceAbout(DeprecatedOperations::eOrientationEvent);
528 break;
529 case nsIDeviceSensorData::TYPE_PROXIMITY:
530 // checks "device.sensors.proximity.enabled" pref
531 if (!StaticPrefs::device_sensors_proximity_enabled()) {
532 return false;
534 if (doc) {
535 doc->WarnOnceAbout(DeprecatedOperations::eProximityEvent, true);
537 break;
538 case nsIDeviceSensorData::TYPE_LIGHT:
539 // checks "device.sensors.ambientLight.enabled" pref
540 if (!StaticPrefs::device_sensors_ambientLight_enabled()) {
541 return false;
543 if (doc) {
544 doc->WarnOnceAbout(DeprecatedOperations::eAmbientLightEvent, true);
546 break;
547 default:
548 MOZ_ASSERT_UNREACHABLE("Device sensor type not recognised");
549 return false;
552 if (!window) {
553 return true;
555 return !nsGlobalWindowInner::Cast(window)->ShouldResistFingerprinting();