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