diff options
Diffstat (limited to 'dom/system/nsDeviceSensors.cpp')
-rw-r--r-- | dom/system/nsDeviceSensors.cpp | 557 |
1 files changed, 557 insertions, 0 deletions
diff --git a/dom/system/nsDeviceSensors.cpp b/dom/system/nsDeviceSensors.cpp new file mode 100644 index 0000000000..c7fc67b52c --- /dev/null +++ b/dom/system/nsDeviceSensors.cpp @@ -0,0 +1,557 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/Hal.h" +#include "mozilla/HalSensor.h" + +#include "nsContentUtils.h" +#include "nsDeviceSensors.h" + +#include "nsGlobalWindowInner.h" +#include "nsPIDOMWindow.h" +#include "nsIScriptObjectPrincipal.h" +#include "mozilla/Preferences.h" +#include "mozilla/StaticPrefs_device.h" +#include "mozilla/Attributes.h" +#include "mozilla/dom/BrowsingContext.h" +#include "mozilla/dom/DeviceLightEvent.h" +#include "mozilla/dom/DeviceOrientationEvent.h" +#include "mozilla/dom/Document.h" +#include "mozilla/dom/Event.h" +#include "mozilla/dom/UserProximityEvent.h" +#include "mozilla/ErrorResult.h" + +#include <cmath> + +using namespace mozilla; +using namespace mozilla::dom; +using namespace hal; + +class nsIDOMWindow; + +#undef near + +#define DEFAULT_SENSOR_POLL 100 + +static const nsTArray<nsIDOMWindow*>::index_type NoIndex = + nsTArray<nsIDOMWindow*>::NoIndex; + +class nsDeviceSensorData final : public nsIDeviceSensorData { + public: + NS_DECL_ISUPPORTS + NS_DECL_NSIDEVICESENSORDATA + + nsDeviceSensorData(unsigned long type, double x, double y, double z); + + private: + ~nsDeviceSensorData(); + + protected: + unsigned long mType; + double mX, mY, mZ; +}; + +nsDeviceSensorData::nsDeviceSensorData(unsigned long type, double x, double y, + double z) + : mType(type), mX(x), mY(y), mZ(z) {} + +NS_INTERFACE_MAP_BEGIN(nsDeviceSensorData) + NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIDeviceSensorData) +NS_INTERFACE_MAP_END + +NS_IMPL_ADDREF(nsDeviceSensorData) +NS_IMPL_RELEASE(nsDeviceSensorData) + +nsDeviceSensorData::~nsDeviceSensorData() = default; + +NS_IMETHODIMP nsDeviceSensorData::GetType(uint32_t* aType) { + NS_ENSURE_ARG_POINTER(aType); + *aType = mType; + return NS_OK; +} + +NS_IMETHODIMP nsDeviceSensorData::GetX(double* aX) { + NS_ENSURE_ARG_POINTER(aX); + *aX = mX; + return NS_OK; +} + +NS_IMETHODIMP nsDeviceSensorData::GetY(double* aY) { + NS_ENSURE_ARG_POINTER(aY); + *aY = mY; + return NS_OK; +} + +NS_IMETHODIMP nsDeviceSensorData::GetZ(double* aZ) { + NS_ENSURE_ARG_POINTER(aZ); + *aZ = mZ; + return NS_OK; +} + +NS_IMPL_ISUPPORTS(nsDeviceSensors, nsIDeviceSensors) + +nsDeviceSensors::nsDeviceSensors() { + mIsUserProximityNear = false; + mLastDOMMotionEventTime = TimeStamp::Now(); + + for (int i = 0; i < NUM_SENSOR_TYPE; i++) { + nsTArray<nsIDOMWindow*>* windows = new nsTArray<nsIDOMWindow*>(); + mWindowListeners.AppendElement(windows); + } + + mLastDOMMotionEventTime = TimeStamp::Now(); +} + +nsDeviceSensors::~nsDeviceSensors() { + for (int i = 0; i < NUM_SENSOR_TYPE; i++) { + if (IsSensorEnabled(i)) UnregisterSensorObserver((SensorType)i, this); + } + + for (int i = 0; i < NUM_SENSOR_TYPE; i++) { + delete mWindowListeners[i]; + } +} + +NS_IMETHODIMP nsDeviceSensors::HasWindowListener(uint32_t aType, + nsIDOMWindow* aWindow, + bool* aRetVal) { + if (!IsSensorAllowedByPref(aType, aWindow)) + *aRetVal = false; + else + *aRetVal = mWindowListeners[aType]->IndexOf(aWindow) != NoIndex; + + return NS_OK; +} + +class DeviceSensorTestEvent : public Runnable { + public: + DeviceSensorTestEvent(nsDeviceSensors* aTarget, uint32_t aType) + : mozilla::Runnable("DeviceSensorTestEvent"), + mTarget(aTarget), + mType(aType) {} + + NS_IMETHOD Run() override { + SensorData sensorData; + sensorData.sensor() = static_cast<SensorType>(mType); + sensorData.timestamp() = PR_Now(); + sensorData.values().AppendElement(0.5f); + sensorData.values().AppendElement(0.5f); + sensorData.values().AppendElement(0.5f); + sensorData.values().AppendElement(0.5f); + mTarget->Notify(sensorData); + return NS_OK; + } + + private: + RefPtr<nsDeviceSensors> mTarget; + uint32_t mType; +}; + +NS_IMETHODIMP nsDeviceSensors::AddWindowListener(uint32_t aType, + nsIDOMWindow* aWindow) { + if (!IsSensorAllowedByPref(aType, aWindow)) return NS_OK; + + if (mWindowListeners[aType]->IndexOf(aWindow) != NoIndex) return NS_OK; + + if (!IsSensorEnabled(aType)) { + RegisterSensorObserver((SensorType)aType, this); + } + + mWindowListeners[aType]->AppendElement(aWindow); + + if (StaticPrefs::device_sensors_test_events()) { + nsCOMPtr<nsIRunnable> event = new DeviceSensorTestEvent(this, aType); + NS_DispatchToCurrentThread(event); + } + + return NS_OK; +} + +NS_IMETHODIMP nsDeviceSensors::RemoveWindowListener(uint32_t aType, + nsIDOMWindow* aWindow) { + if (mWindowListeners[aType]->IndexOf(aWindow) == NoIndex) return NS_OK; + + mWindowListeners[aType]->RemoveElement(aWindow); + + if (mWindowListeners[aType]->Length() == 0) + UnregisterSensorObserver((SensorType)aType, this); + + return NS_OK; +} + +NS_IMETHODIMP nsDeviceSensors::RemoveWindowAsListener(nsIDOMWindow* aWindow) { + for (int i = 0; i < NUM_SENSOR_TYPE; i++) { + RemoveWindowListener((SensorType)i, aWindow); + } + return NS_OK; +} + +static bool WindowCannotReceiveSensorEvent(nsPIDOMWindowInner* aWindow) { + // Check to see if this window is in the background. + if (!aWindow || !aWindow->IsCurrentInnerWindow()) { + return true; + } + + nsPIDOMWindowOuter* windowOuter = aWindow->GetOuterWindow(); + BrowsingContext* topBC = aWindow->GetBrowsingContext()->Top(); + if (windowOuter->IsBackground() || !topBC->GetIsActiveBrowserWindow()) { + return true; + } + + // Check to see if this window is a cross-origin iframe: + if (!topBC->IsInProcess()) { + return true; + } + + nsCOMPtr<nsIScriptObjectPrincipal> sop = do_QueryInterface(aWindow); + nsCOMPtr<nsIScriptObjectPrincipal> topSop = + do_QueryInterface(topBC->GetDOMWindow()); + if (!sop || !topSop) { + return true; + } + + nsIPrincipal* principal = sop->GetPrincipal(); + nsIPrincipal* topPrincipal = topSop->GetPrincipal(); + if (!principal || !topPrincipal) { + return true; + } + + return !principal->Subsumes(topPrincipal); +} + +// Holds the device orientation in Euler angle degrees (azimuth, pitch, roll). +struct Orientation { + enum OrientationReference { kRelative = 0, kAbsolute }; + + static Orientation RadToDeg(const Orientation& aOrient) { + const static double kRadToDeg = 180.0 / M_PI; + return {aOrient.alpha * kRadToDeg, aOrient.beta * kRadToDeg, + aOrient.gamma * kRadToDeg}; + } + + double alpha; + double beta; + double gamma; +}; + +static Orientation RotationVectorToOrientation(double aX, double aY, double aZ, + double aW) { + double mat[9]; + + mat[0] = 1 - 2 * aY * aY - 2 * aZ * aZ; + mat[1] = 2 * aX * aY - 2 * aZ * aW; + mat[2] = 2 * aX * aZ + 2 * aY * aW; + + mat[3] = 2 * aX * aY + 2 * aZ * aW; + mat[4] = 1 - 2 * aX * aX - 2 * aZ * aZ; + mat[5] = 2 * aY * aZ - 2 * aX * aW; + + mat[6] = 2 * aX * aZ - 2 * aY * aW; + mat[7] = 2 * aY * aZ + 2 * aX * aW; + mat[8] = 1 - 2 * aX * aX - 2 * aY * aY; + + Orientation orient; + + if (mat[8] > 0) { + orient.alpha = atan2(-mat[1], mat[4]); + orient.beta = asin(mat[7]); + orient.gamma = atan2(-mat[6], mat[8]); + } else if (mat[8] < 0) { + orient.alpha = atan2(mat[1], -mat[4]); + orient.beta = -asin(mat[7]); + orient.beta += (orient.beta >= 0) ? -M_PI : M_PI; + orient.gamma = atan2(mat[6], -mat[8]); + } else { + if (mat[6] > 0) { + orient.alpha = atan2(-mat[1], mat[4]); + orient.beta = asin(mat[7]); + orient.gamma = -M_PI_2; + } else if (mat[6] < 0) { + orient.alpha = atan2(mat[1], -mat[4]); + orient.beta = -asin(mat[7]); + orient.beta += (orient.beta >= 0) ? -M_PI : M_PI; + orient.gamma = -M_PI_2; + } else { + orient.alpha = atan2(mat[3], mat[0]); + orient.beta = (mat[7] > 0) ? M_PI_2 : -M_PI_2; + orient.gamma = 0; + } + } + + if (orient.alpha < 0) { + orient.alpha += 2 * M_PI; + } + + return Orientation::RadToDeg(orient); +} + +void nsDeviceSensors::Notify(const mozilla::hal::SensorData& aSensorData) { + uint32_t type = aSensorData.sensor(); + + const nsTArray<float>& values = aSensorData.values(); + size_t len = values.Length(); + double x = len > 0 ? values[0] : 0.0; + double y = len > 1 ? values[1] : 0.0; + double z = len > 2 ? values[2] : 0.0; + double w = len > 3 ? values[3] : 0.0; + PRTime timestamp = aSensorData.timestamp(); + + nsCOMArray<nsIDOMWindow> windowListeners; + for (uint32_t i = 0; i < mWindowListeners[type]->Length(); i++) { + windowListeners.AppendObject(mWindowListeners[type]->SafeElementAt(i)); + } + + for (uint32_t i = windowListeners.Count(); i > 0;) { + --i; + + nsCOMPtr<nsPIDOMWindowInner> pwindow = + do_QueryInterface(windowListeners[i]); + if (WindowCannotReceiveSensorEvent(pwindow)) { + continue; + } + + if (nsCOMPtr<Document> doc = pwindow->GetDoc()) { + nsCOMPtr<mozilla::dom::EventTarget> target = + do_QueryInterface(windowListeners[i]); + if (type == nsIDeviceSensorData::TYPE_ACCELERATION || + type == nsIDeviceSensorData::TYPE_LINEAR_ACCELERATION || + type == nsIDeviceSensorData::TYPE_GYROSCOPE) { + FireDOMMotionEvent(doc, target, type, timestamp, x, y, z); + } else if (type == nsIDeviceSensorData::TYPE_ORIENTATION) { + FireDOMOrientationEvent(target, x, y, z, Orientation::kAbsolute); + } else if (type == nsIDeviceSensorData::TYPE_ROTATION_VECTOR) { + const Orientation orient = RotationVectorToOrientation(x, y, z, w); + FireDOMOrientationEvent(target, orient.alpha, orient.beta, orient.gamma, + Orientation::kAbsolute); + } else if (type == nsIDeviceSensorData::TYPE_GAME_ROTATION_VECTOR) { + const Orientation orient = RotationVectorToOrientation(x, y, z, w); + FireDOMOrientationEvent(target, orient.alpha, orient.beta, orient.gamma, + Orientation::kRelative); + } else if (type == nsIDeviceSensorData::TYPE_PROXIMITY) { + MaybeFireDOMUserProximityEvent(target, x, z); + } else if (type == nsIDeviceSensorData::TYPE_LIGHT) { + FireDOMLightEvent(target, x); + } + } + } +} + +void nsDeviceSensors::FireDOMLightEvent(mozilla::dom::EventTarget* aTarget, + double aValue) { + DeviceLightEventInit init; + init.mBubbles = true; + init.mCancelable = false; + init.mValue = round(aValue); + RefPtr<DeviceLightEvent> event = + DeviceLightEvent::Constructor(aTarget, u"devicelight"_ns, init); + + event->SetTrusted(true); + + aTarget->DispatchEvent(*event); +} + +void nsDeviceSensors::MaybeFireDOMUserProximityEvent( + mozilla::dom::EventTarget* aTarget, double aValue, double aMax) { + bool near = (aValue < aMax); + if (mIsUserProximityNear != near) { + mIsUserProximityNear = near; + FireDOMUserProximityEvent(aTarget, mIsUserProximityNear); + } +} + +void nsDeviceSensors::FireDOMUserProximityEvent( + mozilla::dom::EventTarget* aTarget, bool aNear) { + UserProximityEventInit init; + init.mBubbles = true; + init.mCancelable = false; + init.mNear = aNear; + RefPtr<UserProximityEvent> event = + UserProximityEvent::Constructor(aTarget, u"userproximity"_ns, init); + + event->SetTrusted(true); + + aTarget->DispatchEvent(*event); +} + +void nsDeviceSensors::FireDOMOrientationEvent(EventTarget* aTarget, + double aAlpha, double aBeta, + double aGamma, bool aIsAbsolute) { + DeviceOrientationEventInit init; + init.mBubbles = true; + init.mCancelable = false; + init.mAlpha.SetValue(aAlpha); + init.mBeta.SetValue(aBeta); + init.mGamma.SetValue(aGamma); + init.mAbsolute = aIsAbsolute; + + auto Dispatch = [&](EventTarget* aEventTarget, const nsAString& aType) { + RefPtr<DeviceOrientationEvent> event = + DeviceOrientationEvent::Constructor(aEventTarget, aType, init); + event->SetTrusted(true); + aEventTarget->DispatchEvent(*event); + }; + + Dispatch(aTarget, aIsAbsolute ? u"deviceorientationabsolute"_ns + : u"deviceorientation"_ns); + + // This is used to determine whether relative events have been dispatched + // during the current session, in which case we don't dispatch the additional + // compatibility events. + static bool sIsDispatchingRelativeEvents = false; + sIsDispatchingRelativeEvents = sIsDispatchingRelativeEvents || !aIsAbsolute; + + // Android devices with SENSOR_GAME_ROTATION_VECTOR support dispatch + // relative events for "deviceorientation" by default, while other platforms + // and devices without such support dispatch absolute events by default. + if (aIsAbsolute && !sIsDispatchingRelativeEvents) { + // For absolute events on devices without support for relative events, + // we need to additionally dispatch type "deviceorientation" to keep + // backwards-compatibility. + Dispatch(aTarget, u"deviceorientation"_ns); + } +} + +void nsDeviceSensors::FireDOMMotionEvent(Document* doc, EventTarget* target, + uint32_t type, PRTime timestamp, + double x, double y, double z) { + // Attempt to coalesce events + TimeDuration sensorPollDuration = + TimeDuration::FromMilliseconds(DEFAULT_SENSOR_POLL); + bool fireEvent = + (TimeStamp::Now() > mLastDOMMotionEventTime + sensorPollDuration) || + StaticPrefs::device_sensors_test_events(); + + switch (type) { + case nsIDeviceSensorData::TYPE_LINEAR_ACCELERATION: + if (!mLastAcceleration) { + mLastAcceleration.emplace(); + } + mLastAcceleration->mX.SetValue(x); + mLastAcceleration->mY.SetValue(y); + mLastAcceleration->mZ.SetValue(z); + break; + case nsIDeviceSensorData::TYPE_ACCELERATION: + if (!mLastAccelerationIncludingGravity) { + mLastAccelerationIncludingGravity.emplace(); + } + mLastAccelerationIncludingGravity->mX.SetValue(x); + mLastAccelerationIncludingGravity->mY.SetValue(y); + mLastAccelerationIncludingGravity->mZ.SetValue(z); + break; + case nsIDeviceSensorData::TYPE_GYROSCOPE: + if (!mLastRotationRate) { + mLastRotationRate.emplace(); + } + mLastRotationRate->mAlpha.SetValue(x); + mLastRotationRate->mBeta.SetValue(y); + mLastRotationRate->mGamma.SetValue(z); + break; + } + + if (fireEvent) { + if (!mLastAcceleration) { + mLastAcceleration.emplace(); + } + if (!mLastAccelerationIncludingGravity) { + mLastAccelerationIncludingGravity.emplace(); + } + if (!mLastRotationRate) { + mLastRotationRate.emplace(); + } + } else if (!mLastAcceleration || !mLastAccelerationIncludingGravity || + !mLastRotationRate) { + return; + } + + IgnoredErrorResult ignored; + RefPtr<Event> event = + doc->CreateEvent(u"DeviceMotionEvent"_ns, CallerType::System, ignored); + if (!event) { + return; + } + + DeviceMotionEvent* me = static_cast<DeviceMotionEvent*>(event.get()); + + me->InitDeviceMotionEvent( + u"devicemotion"_ns, true, false, *mLastAcceleration, + *mLastAccelerationIncludingGravity, *mLastRotationRate, + Nullable<double>(DEFAULT_SENSOR_POLL), Nullable<uint64_t>(timestamp)); + + event->SetTrusted(true); + + target->DispatchEvent(*event); + + mLastRotationRate.reset(); + mLastAccelerationIncludingGravity.reset(); + mLastAcceleration.reset(); + mLastDOMMotionEventTime = TimeStamp::Now(); +} + +bool nsDeviceSensors::IsSensorAllowedByPref(uint32_t aType, + nsIDOMWindow* aWindow) { + // checks "device.sensors.enabled" master pref + if (!StaticPrefs::device_sensors_enabled()) { + return false; + } + + nsCOMPtr<nsPIDOMWindowInner> window = do_QueryInterface(aWindow); + nsCOMPtr<Document> doc; + if (window) { + doc = window->GetExtantDoc(); + } + + switch (aType) { + case nsIDeviceSensorData::TYPE_LINEAR_ACCELERATION: + case nsIDeviceSensorData::TYPE_ACCELERATION: + case nsIDeviceSensorData::TYPE_GYROSCOPE: + // checks "device.sensors.motion.enabled" pref + if (!StaticPrefs::device_sensors_motion_enabled()) { + return false; + } + if (doc) { + doc->WarnOnceAbout(DeprecatedOperations::eMotionEvent); + } + break; + case nsIDeviceSensorData::TYPE_GAME_ROTATION_VECTOR: + case nsIDeviceSensorData::TYPE_ORIENTATION: + case nsIDeviceSensorData::TYPE_ROTATION_VECTOR: + // checks "device.sensors.orientation.enabled" pref + if (!StaticPrefs::device_sensors_orientation_enabled()) { + return false; + } + if (doc) { + doc->WarnOnceAbout(DeprecatedOperations::eOrientationEvent); + } + break; + case nsIDeviceSensorData::TYPE_PROXIMITY: + // checks "device.sensors.proximity.enabled" pref + if (!StaticPrefs::device_sensors_proximity_enabled()) { + return false; + } + if (doc) { + doc->WarnOnceAbout(DeprecatedOperations::eProximityEvent, true); + } + break; + case nsIDeviceSensorData::TYPE_LIGHT: + // checks "device.sensors.ambientLight.enabled" pref + if (!StaticPrefs::device_sensors_ambientLight_enabled()) { + return false; + } + if (doc) { + doc->WarnOnceAbout(DeprecatedOperations::eAmbientLightEvent, true); + } + break; + default: + MOZ_ASSERT_UNREACHABLE("Device sensor type not recognised"); + return false; + } + + if (!window) { + return true; + } + return !nsGlobalWindowInner::Cast(window)->ShouldResistFingerprinting( + RFPTarget::DeviceSensors); +} |