diff --git a/dom/bindings/TypedArray.h b/dom/bindings/TypedArray.h
index 9d290068eb7b6688537c84ea827829aae2b73ef3..fa2e63cf59acd3725fcb0645da8a48c628fe2c25 100644
--- a/dom/bindings/TypedArray.h
+++ b/dom/bindings/TypedArray.h
@@ -475,6 +475,25 @@ struct TypedArray_base : public SpiderMonkeyInterfaceObjectStorage,
         std::forward<Calculator>(aCalculator)...);
   }
 
+  template <typename T, size_t N, typename... Calculator>
+  [[nodiscard]] bool CopyDataTo(std::array<T, N>* const aResult,
+                                Calculator&&... aCalculator) const {
+    static_assert(sizeof...(aCalculator) <= 1,
+                  "CopyDataTo takes at most one aCalculator");
+
+    return ProcessDataHelper(
+        [&](const Span<const element_type>& aData, JS::AutoCheckCannotGC&&) {
+          if (aData.Length() != N) {
+            return false;
+          }
+          for (size_t i = 0; i < N; ++i) {
+            (*aResult).at(i) = aData[i];
+          }
+          return true;
+        },
+        std::forward<Calculator>(aCalculator)...);
+  }
+
   /**
    * Helper functions to copy this typed array's data to a newly created
    * container. Returns Nothing() if creating the container with the right size
diff --git a/dom/canvas/TiedFields.h b/dom/canvas/TiedFields.h
index b273aec3a8ec9d57c7f75483144240b759dc8f0a..20c3d635f0003647d40f674981a90c9c17644ac8 100644
--- a/dom/canvas/TiedFields.h
+++ b/dom/canvas/TiedFields.h
@@ -7,6 +7,8 @@
 
 #include "TupleUtils.h"
 
+#include <array>
+
 namespace mozilla {
 
 // -
diff --git a/dom/vr/VRDisplay.cpp b/dom/vr/VRDisplay.cpp
index 4fe272e33851a75e7a52c512ad30e70487609136..740d9dba3da437f4eac3ad688fa46e45334be6b0 100644
--- a/dom/vr/VRDisplay.cpp
+++ b/dom/vr/VRDisplay.cpp
@@ -236,7 +236,7 @@ void VRPose::GetPosition(JSContext* aCx, JS::MutableHandle<JSObject*> aRetval,
       bool(mVRState.flags &
            gfx::VRDisplayCapabilityFlags::Cap_PositionEmulated);
   SetFloat32Array(aCx, this, aRetval, mPosition,
-                  valid ? mVRState.pose.position : nullptr, 3, aRv);
+                  valid ? mVRState.pose.position.data() : nullptr, 3, aRv);
 }
 
 void VRPose::GetLinearVelocity(JSContext* aCx,
@@ -247,7 +247,8 @@ void VRPose::GetLinearVelocity(JSContext* aCx,
       bool(mVRState.flags &
            gfx::VRDisplayCapabilityFlags::Cap_PositionEmulated);
   SetFloat32Array(aCx, this, aRetval, mLinearVelocity,
-                  valid ? mVRState.pose.linearVelocity : nullptr, 3, aRv);
+                  valid ? mVRState.pose.linearVelocity.data() : nullptr, 3,
+                  aRv);
 }
 
 void VRPose::GetLinearAcceleration(JSContext* aCx,
@@ -256,7 +257,8 @@ void VRPose::GetLinearAcceleration(JSContext* aCx,
   const bool valid = bool(
       mVRState.flags & gfx::VRDisplayCapabilityFlags::Cap_LinearAcceleration);
   SetFloat32Array(aCx, this, aRetval, mLinearAcceleration,
-                  valid ? mVRState.pose.linearAcceleration : nullptr, 3, aRv);
+                  valid ? mVRState.pose.linearAcceleration.data() : nullptr, 3,
+                  aRv);
 }
 
 void VRPose::GetOrientation(JSContext* aCx,
@@ -265,7 +267,7 @@ void VRPose::GetOrientation(JSContext* aCx,
   const bool valid =
       bool(mVRState.flags & gfx::VRDisplayCapabilityFlags::Cap_Orientation);
   SetFloat32Array(aCx, this, aRetval, mOrientation,
-                  valid ? mVRState.pose.orientation : nullptr, 4, aRv);
+                  valid ? mVRState.pose.orientation.data() : nullptr, 4, aRv);
 }
 
 void VRPose::GetAngularVelocity(JSContext* aCx,
@@ -274,7 +276,8 @@ void VRPose::GetAngularVelocity(JSContext* aCx,
   const bool valid =
       bool(mVRState.flags & gfx::VRDisplayCapabilityFlags::Cap_Orientation);
   SetFloat32Array(aCx, this, aRetval, mAngularVelocity,
-                  valid ? mVRState.pose.angularVelocity : nullptr, 3, aRv);
+                  valid ? mVRState.pose.angularVelocity.data() : nullptr, 3,
+                  aRv);
 }
 
 void VRPose::GetAngularAcceleration(JSContext* aCx,
@@ -283,7 +286,8 @@ void VRPose::GetAngularAcceleration(JSContext* aCx,
   const bool valid = bool(
       mVRState.flags & gfx::VRDisplayCapabilityFlags::Cap_AngularAcceleration);
   SetFloat32Array(aCx, this, aRetval, mAngularAcceleration,
-                  valid ? mVRState.pose.angularAcceleration : nullptr, 3, aRv);
+                  valid ? mVRState.pose.angularAcceleration.data() : nullptr, 3,
+                  aRv);
 }
 
 void VRPose::Update(const gfx::VRHMDSensorState& aState) { mVRState = aState; }
@@ -759,9 +763,9 @@ void VRFrameInfo::Update(const gfx::VRDisplayInfo& aInfo,
       aInfo.mDisplayState.eyeFOV[gfx::VRDisplayState::Eye_Right];
   mRightProjection =
       rightFOV.ConstructProjectionMatrix(aDepthNear, aDepthFar, true);
-  memcpy(mLeftView.components, aState.leftViewMatrix,
+  memcpy(mLeftView.components, aState.leftViewMatrix.data(),
          sizeof(aState.leftViewMatrix));
-  memcpy(mRightView.components, aState.rightViewMatrix,
+  memcpy(mRightView.components, aState.rightViewMatrix.data(),
          sizeof(aState.rightViewMatrix));
 }
 
diff --git a/dom/vr/VRServiceTest.cpp b/dom/vr/VRServiceTest.cpp
index 4e7691638d366cc871e10033a8a716a9b8d42f00..085b7b339d3a59c731e1826f3ee864a8cbbd8bf3 100644
--- a/dom/vr/VRServiceTest.cpp
+++ b/dom/vr/VRServiceTest.cpp
@@ -24,7 +24,7 @@ NS_IMPL_ISUPPORTS_CYCLE_COLLECTION_INHERITED_0(VRMockDisplay,
 
 namespace {
 template <class T>
-bool ReadFloat32Array(T& aDestination, const Float32Array& aSource,
+bool ReadFloat32Array(T* aDestination, const Float32Array& aSource,
                       ErrorResult& aRv) {
   if (!aSource.CopyDataTo(aDestination)) {
     aRv.Throw(NS_ERROR_INVALID_ARG);
@@ -65,7 +65,7 @@ void VRMockDisplay::Create() {
   Clear();
   VRDisplayState& state = DisplayState();
 
-  strncpy(state.displayName, "Puppet HMD", kVRDisplayNameMaxLen);
+  strncpy(state.displayName.data(), "Puppet HMD", kVRDisplayNameMaxLen);
   state.eightCC = GFX_VR_EIGHTCC('P', 'u', 'p', 'p', 'e', 't', ' ', ' ');
   state.isConnected = true;
   state.isMounted = false;
@@ -271,7 +271,7 @@ void VRMockDisplay::SetStageSize(double aWidth, double aHeight) {
 
 void VRMockDisplay::SetSittingToStandingTransform(
     const Float32Array& aTransform, ErrorResult& aRv) {
-  Unused << ReadFloat32Array(DisplayState().sittingToStandingTransform,
+  Unused << ReadFloat32Array(&DisplayState().sittingToStandingTransform,
                              aTransform, aRv);
 }
 
@@ -289,41 +289,41 @@ void VRMockDisplay::SetPose(const Nullable<Float32Array>& aPosition,
   // puppet script execution
 
   if (!aOrientation.IsNull()) {
-    if (!ReadFloat32Array(sensorState.pose.orientation, aOrientation.Value(),
+    if (!ReadFloat32Array(&sensorState.pose.orientation, aOrientation.Value(),
                           aRv)) {
       return;
     }
     sensorState.flags |= VRDisplayCapabilityFlags::Cap_Orientation;
   }
   if (!aAngularVelocity.IsNull()) {
-    if (!ReadFloat32Array(sensorState.pose.angularVelocity,
+    if (!ReadFloat32Array(&sensorState.pose.angularVelocity,
                           aAngularVelocity.Value(), aRv)) {
       return;
     }
     sensorState.flags |= VRDisplayCapabilityFlags::Cap_AngularAcceleration;
   }
   if (!aAngularAcceleration.IsNull()) {
-    if (!ReadFloat32Array(sensorState.pose.angularAcceleration,
+    if (!ReadFloat32Array(&sensorState.pose.angularAcceleration,
                           aAngularAcceleration.Value(), aRv)) {
       return;
     }
     sensorState.flags |= VRDisplayCapabilityFlags::Cap_AngularAcceleration;
   }
   if (!aPosition.IsNull()) {
-    if (!ReadFloat32Array(sensorState.pose.position, aPosition.Value(), aRv)) {
+    if (!ReadFloat32Array(&sensorState.pose.position, aPosition.Value(), aRv)) {
       return;
     }
     sensorState.flags |= VRDisplayCapabilityFlags::Cap_Position;
   }
   if (!aLinearVelocity.IsNull()) {
-    if (!ReadFloat32Array(sensorState.pose.linearVelocity,
+    if (!ReadFloat32Array(&sensorState.pose.linearVelocity,
                           aLinearVelocity.Value(), aRv)) {
       return;
     }
     sensorState.flags |= VRDisplayCapabilityFlags::Cap_LinearAcceleration;
   }
   if (!aLinearAcceleration.IsNull()) {
-    if (!ReadFloat32Array(sensorState.pose.linearAcceleration,
+    if (!ReadFloat32Array(&sensorState.pose.linearAcceleration,
                           aLinearAcceleration.Value(), aRv)) {
       return;
     }
@@ -360,7 +360,8 @@ void VRMockController::Create() {
   // puppet.
   Clear();
   VRControllerState& state = ControllerState();
-  strncpy(state.controllerName, "Puppet Gamepad", kVRControllerNameMaxLen);
+  strncpy(state.controllerName.data(), "Puppet Gamepad",
+          kVRControllerNameMaxLen);
   state.hand = GamepadHand::Left;
   state.flags = GamepadCapabilityFlags::Cap_Position |
                 GamepadCapabilityFlags::Cap_Orientation;
@@ -488,42 +489,42 @@ void VRMockController::SetPose(
   controllerState.flags = GamepadCapabilityFlags::Cap_None;
 
   if (!aOrientation.IsNull()) {
-    if (!ReadFloat32Array(controllerState.pose.orientation,
+    if (!ReadFloat32Array(&controllerState.pose.orientation,
                           aOrientation.Value(), aRv)) {
       return;
     }
     controllerState.flags |= GamepadCapabilityFlags::Cap_Orientation;
   }
   if (!aAngularVelocity.IsNull()) {
-    if (!ReadFloat32Array(controllerState.pose.angularVelocity,
+    if (!ReadFloat32Array(&controllerState.pose.angularVelocity,
                           aAngularVelocity.Value(), aRv)) {
       return;
     }
     controllerState.flags |= GamepadCapabilityFlags::Cap_AngularAcceleration;
   }
   if (!aAngularAcceleration.IsNull()) {
-    if (!ReadFloat32Array(controllerState.pose.angularAcceleration,
+    if (!ReadFloat32Array(&controllerState.pose.angularAcceleration,
                           aAngularAcceleration.Value(), aRv)) {
       return;
     }
     controllerState.flags |= GamepadCapabilityFlags::Cap_AngularAcceleration;
   }
   if (!aPosition.IsNull()) {
-    if (!ReadFloat32Array(controllerState.pose.position, aPosition.Value(),
+    if (!ReadFloat32Array(&controllerState.pose.position, aPosition.Value(),
                           aRv)) {
       return;
     }
     controllerState.flags |= GamepadCapabilityFlags::Cap_Position;
   }
   if (!aLinearVelocity.IsNull()) {
-    if (!ReadFloat32Array(controllerState.pose.linearVelocity,
+    if (!ReadFloat32Array(&controllerState.pose.linearVelocity,
                           aLinearVelocity.Value(), aRv)) {
       return;
     }
     controllerState.flags |= GamepadCapabilityFlags::Cap_LinearAcceleration;
   }
   if (!aLinearAcceleration.IsNull()) {
-    if (!ReadFloat32Array(controllerState.pose.linearAcceleration,
+    if (!ReadFloat32Array(&controllerState.pose.linearAcceleration,
                           aLinearAcceleration.Value(), aRv)) {
       return;
     }
diff --git a/gfx/vr/VRDisplayClient.cpp b/gfx/vr/VRDisplayClient.cpp
index 914fb05893c34c94f38f2c0299d5d75e0f805d7f..d1ecc34b0487ef9550dad26d3a05b4dffc6033d5 100644
--- a/gfx/vr/VRDisplayClient.cpp
+++ b/gfx/vr/VRDisplayClient.cpp
@@ -175,9 +175,7 @@ void VRDisplayClient::FireEvents() {
 
 void VRDisplayClient::GamepadMappingForWebVR(
     VRControllerState& aControllerState) {
-  float triggerValue[kVRControllerMaxButtons];
-  memcpy(triggerValue, aControllerState.triggerValue,
-         sizeof(aControllerState.triggerValue));
+  auto triggerValue = aControllerState.triggerValue;
   const uint64_t buttonPressed = aControllerState.buttonPressed;
   const uint64_t buttonTouched = aControllerState.buttonTouched;
 
@@ -481,7 +479,7 @@ void VRDisplayClient::FireGamepadEvents() {
     // axis count. So, we need to check if they are more than zero.
     if ((lastState.controllerName[0] == '\0' || !existing) &&
         (state.numButtons > 0 || state.numAxes > 0)) {
-      dom::GamepadAdded info(NS_ConvertUTF8toUTF16(state.controllerName),
+      dom::GamepadAdded info(NS_ConvertUTF8toUTF16(state.controllerName.data()),
                              dom::GamepadMappingType::_empty, state.hand,
                              mDisplayInfo.mDisplayID, state.numButtons,
                              state.numAxes, state.numHaptics, 0, 0);
@@ -578,10 +576,7 @@ void VRDisplayClient::FireGamepadEvents() {
     }
   }
 
-  // Note that VRControllerState is asserted to be a POD type and memcpy is
-  // safe.
-  memcpy(mLastEventControllerState, mDisplayInfo.mControllerState,
-         sizeof(VRControllerState) * kVRControllerMaxCount);
+  mLastEventControllerState = mDisplayInfo.mControllerState;
 }
 
 const VRHMDSensorState& VRDisplayClient::GetSensorState() const {
diff --git a/gfx/vr/VRDisplayClient.h b/gfx/vr/VRDisplayClient.h
index 19f8fdc41384e3b30a1c002502acc78d430c8c2c..c3a8c13023da9203620f90443db689fbfe89daab 100644
--- a/gfx/vr/VRDisplayClient.h
+++ b/gfx/vr/VRDisplayClient.h
@@ -7,12 +7,13 @@
 #ifndef GFX_VR_DISPLAY_CLIENT_H
 #define GFX_VR_DISPLAY_CLIENT_H
 
-#include "nsCOMPtr.h"
+#include "gfxVR.h"
 #include "mozilla/Attributes.h"
 #include "mozilla/RefPtr.h"
 #include "mozilla/dom/VRDisplayBinding.h"
+#include "nsCOMPtr.h"
 
-#include "gfxVR.h"
+#include <array>
 
 namespace mozilla {
 namespace dom {
@@ -79,7 +80,8 @@ class VRDisplayClient {
   // Difference between mDisplayInfo.mControllerState and
   // mLastEventControllerState determines what gamepad events to fire when
   // updated.
-  VRControllerState mLastEventControllerState[kVRControllerMaxCount];
+  std::array<VRControllerState, kVRControllerMaxCount>
+      mLastEventControllerState;
 
   /**
    * mSessions is cleared in VRDisplayClient::SessionEnded.
diff --git a/gfx/vr/VRManager.cpp b/gfx/vr/VRManager.cpp
index 5e3f349a60f01986bd206b6455080dee4905ebea..32296dfeaab113ef1bde65a05d493734b2731414 100644
--- a/gfx/vr/VRManager.cpp
+++ b/gfx/vr/VRManager.cpp
@@ -1043,7 +1043,7 @@ void VRManager::PullState(
     const std::function<bool()>& aWaitCondition /* = nullptr */) {
   if (mShmem != nullptr) {
     mShmem->PullSystemState(mDisplayInfo.mDisplayState, mLastSensorState,
-                            mDisplayInfo.mControllerState,
+                            &mDisplayInfo.mControllerState,
                             mEnumerationCompleted, aWaitCondition);
   }
 }
diff --git a/gfx/vr/VRShMem.cpp b/gfx/vr/VRShMem.cpp
index b7d071cb882119fe1546a7416b2dfae0785599c5..9295b5896616537d4dd44bf91f7807faaf1a22b0 100644
--- a/gfx/vr/VRShMem.cpp
+++ b/gfx/vr/VRShMem.cpp
@@ -526,7 +526,8 @@ void VRShMem::PushSystemState(const mozilla::gfx::VRSystemState& aState) {
 #if defined(MOZ_WIDGET_ANDROID)
 void VRShMem::PullSystemState(
     VRDisplayState& aDisplayState, VRHMDSensorState& aSensorState,
-    VRControllerState (&aControllerState)[kVRControllerMaxCount],
+    std::array<VRControllerState, kVRControllerMaxCount>* const
+        aControllerState,
     bool& aEnumerationCompleted,
     const std::function<bool()>& aWaitCondition /* = nullptr */) {
   if (!mExternalShmem) {
@@ -541,9 +542,9 @@ void VRShMem::PullSystemState(
                sizeof(VRDisplayState));
         memcpy(&aSensorState, (void*)&(mExternalShmem->state.sensorState),
                sizeof(VRHMDSensorState));
-        memcpy(aControllerState,
+        memcpy(aControllerState->data(),
                (void*)&(mExternalShmem->state.controllerState),
-               sizeof(VRControllerState) * kVRControllerMaxCount);
+               sizeof(aControllerState->at(0)) * aControllerState->size());
         aEnumerationCompleted = mExternalShmem->state.enumerationCompleted;
         if (!aWaitCondition || aWaitCondition()) {
           done = true;
@@ -565,7 +566,8 @@ void VRShMem::PullSystemState(
 #else
 void VRShMem::PullSystemState(
     VRDisplayState& aDisplayState, VRHMDSensorState& aSensorState,
-    VRControllerState (&aControllerState)[kVRControllerMaxCount],
+    std::array<VRControllerState, kVRControllerMaxCount>* const
+        aControllerState,
     bool& aEnumerationCompleted,
     const std::function<bool()>& aWaitCondition /* = nullptr */) {
   MOZ_ASSERT(mExternalShmem);
@@ -589,10 +591,8 @@ void VRShMem::PullSystemState(
                  sizeof(VRDisplayState));
           memcpy(&aSensorState, &tmp.state.sensorState,
                  sizeof(VRHMDSensorState));
-          memcpy(aControllerState,
-                 (void*)&(mExternalShmem->state.controllerState),
-                 sizeof(VRControllerState) * kVRControllerMaxCount);
-          aEnumerationCompleted = mExternalShmem->state.enumerationCompleted;
+          *aControllerState = tmp.state.controllerState;
+          aEnumerationCompleted = tmp.state.enumerationCompleted;
           // Check for wait condition
           if (!aWaitCondition || aWaitCondition()) {
             return;
diff --git a/gfx/vr/VRShMem.h b/gfx/vr/VRShMem.h
index a10b8189a81ad35260156021c8bc508e5acb8a59..a6819798b6718cc465fbbe2a81faaeb686149843 100644
--- a/gfx/vr/VRShMem.h
+++ b/gfx/vr/VRShMem.h
@@ -37,11 +37,11 @@ class VRShMem final {
   void PullBrowserState(mozilla::gfx::VRBrowserState& aState);
 
   void PushSystemState(const mozilla::gfx::VRSystemState& aState);
-  void PullSystemState(
-      VRDisplayState& aDisplayState, VRHMDSensorState& aSensorState,
-      VRControllerState (&aControllerState)[kVRControllerMaxCount],
-      bool& aEnumerationCompleted,
-      const std::function<bool()>& aWaitCondition = nullptr);
+  void PullSystemState(VRDisplayState& aDisplayState,
+                       VRHMDSensorState& aSensorState,
+                       std::array<VRControllerState, kVRControllerMaxCount>*,
+                       bool& aEnumerationCompleted,
+                       const std::function<bool()>& aWaitCondition = nullptr);
 
   void PushWindowState(VRWindowState& aState);
   void PullWindowState(VRWindowState& aState);
diff --git a/gfx/vr/external_api/moz_external_vr.h b/gfx/vr/external_api/moz_external_vr.h
index 1777c57b9b467836617ffb3d0c921253ab8e8a28..e3049f3ce3c1212da43e4a497903613eb0ec9bc6 100644
--- a/gfx/vr/external_api/moz_external_vr.h
+++ b/gfx/vr/external_api/moz_external_vr.h
@@ -18,16 +18,17 @@
 #  include <stdlib.h>
 #  include <string.h>
 #  include "mozilla/TypedEnumBits.h"
+#  include "mozilla/dom/TiedFields.h"
 #  include "mozilla/gfx/2D.h"
 #  include <stddef.h>
 #  include <stdint.h>
-#  include <type_traits>
 #endif  // MOZILLA_INTERNAL_API
 
 #if defined(__ANDROID__)
 #  include <pthread.h>
 #endif  // defined(__ANDROID__)
 
+#include <array>
 #include <cstdint>
 #include <type_traits>
 
@@ -47,8 +48,8 @@ namespace gfx {
 // and mapped files if we have both release and nightlies
 // running at the same time? Or...what if we have multiple
 // release builds running on same machine? (Bug 1563232)
-#define SHMEM_VERSION "0.0.11"
-static const int32_t kVRExternalVersion = 18;
+#define SHMEM_VERSION "0.0.12"
+static const int32_t kVRExternalVersion = 19;
 
 // We assign VR presentations to groups with a bitmask.
 // Currently, we will only display either content or chrome.
@@ -81,16 +82,40 @@ struct Point3D_POD {
   float x;
   float y;
   float z;
+
+#ifdef MOZILLA_INTERNAL_API
+  auto MutTiedFields() { return std::tie(x, y, z); }
+
+  bool operator==(const Point3D_POD& other) const {
+    return TiedFields(*this) == TiedFields(other);
+  }
+#endif
 };
 
 struct IntSize_POD {
   int32_t width;
   int32_t height;
+
+#ifdef MOZILLA_INTERNAL_API
+  auto MutTiedFields() { return std::tie(width, height); }
+
+  bool operator==(const IntSize_POD& other) const {
+    return TiedFields(*this) == TiedFields(other);
+  }
+#endif
 };
 
 struct FloatSize_POD {
   float width;
   float height;
+
+#ifdef MOZILLA_INTERNAL_API
+  auto MutTiedFields() { return std::tie(width, height); }
+
+  bool operator==(const FloatSize_POD& other) const {
+    return TiedFields(*this) == TiedFields(other);
+  }
+#endif
 };
 
 #ifndef MOZILLA_INTERNAL_API
@@ -154,12 +179,72 @@ enum class VRControllerType : uint8_t {
   _end
 };
 
+inline constexpr bool IsEnumCase(const VRControllerType raw) {
+  switch (raw) {
+    case VRControllerType::_empty:
+    case VRControllerType::HTCVive:
+    case VRControllerType::HTCViveCosmos:
+    case VRControllerType::HTCViveFocus:
+    case VRControllerType::HTCViveFocusPlus:
+    case VRControllerType::MSMR:
+    case VRControllerType::ValveIndex:
+    case VRControllerType::OculusGo:
+    case VRControllerType::OculusTouch:
+    case VRControllerType::OculusTouch2:
+    case VRControllerType::OculusTouch3:
+    case VRControllerType::PicoGaze:
+    case VRControllerType::PicoG2:
+    case VRControllerType::PicoNeo2:
+    case VRControllerType::_end:
+      return true;
+  }
+  return false;
+}
+
+// -
+
 enum class TargetRayMode : uint8_t { Gaze, TrackedPointer, Screen };
 
+inline constexpr bool IsEnumCase(const TargetRayMode raw) {
+  switch (raw) {
+    case TargetRayMode::Gaze:
+    case TargetRayMode::TrackedPointer:
+    case TargetRayMode::Screen:
+      return true;
+  }
+  return false;
+}
+
+// -
+
 enum class GamepadMappingType : uint8_t { _empty, Standard, XRStandard };
 
+inline constexpr bool IsEnumCase(const GamepadMappingType raw) {
+  switch (raw) {
+    case GamepadMappingType::_empty:
+    case GamepadMappingType::Standard:
+    case GamepadMappingType::XRStandard:
+      return true;
+  }
+  return false;
+}
+
+// -
+
 enum class VRDisplayBlendMode : uint8_t { Opaque, Additive, AlphaBlend };
 
+inline constexpr bool IsEnumCase(const VRDisplayBlendMode raw) {
+  switch (raw) {
+    case VRDisplayBlendMode::Opaque:
+    case VRDisplayBlendMode::Additive:
+    case VRDisplayBlendMode::AlphaBlend:
+      return true;
+  }
+  return false;
+}
+
+// -
+
 enum class VRDisplayCapabilityFlags : uint16_t {
   Cap_None = 0,
   /**
@@ -249,26 +334,43 @@ MOZ_MAKE_ENUM_CLASS_BITWISE_OPERATORS(VRDisplayBlendMode)
 #endif  // MOZILLA_INTERNAL_API
 
 struct VRPose {
-  float orientation[4];
-  float position[3];
-  float angularVelocity[3];
-  float angularAcceleration[3];
-  float linearVelocity[3];
-  float linearAcceleration[3];
+  std::array<float, 4> orientation;
+  std::array<float, 3> position;
+  std::array<float, 3> angularVelocity;
+  std::array<float, 3> angularAcceleration;
+  std::array<float, 3> linearVelocity;
+  std::array<float, 3> linearAcceleration;
+
+#ifdef MOZILLA_INTERNAL_API
+  auto MutTiedFields() {
+    return std::tie(orientation, position, angularVelocity, angularAcceleration,
+                    linearVelocity, linearAcceleration);
+  }
+
+  bool operator==(const VRPose& other) const {
+    return TiedFields(*this) == TiedFields(other);
+  }
+#endif  // MOZILLA_INTERNAL_API
 };
 
 struct VRHMDSensorState {
   uint64_t inputFrameID;
   double timestamp;
   VRDisplayCapabilityFlags flags;
+  uint16_t _padding;
 
   // These members will only change with inputFrameID:
   VRPose pose;
-  float leftViewMatrix[16];
-  float rightViewMatrix[16];
+  std::array<float, 16> leftViewMatrix;
+  std::array<float, 16> rightViewMatrix;
 
 #ifdef MOZILLA_INTERNAL_API
 
+  auto MutTiedFields() {
+    return std::tie(inputFrameID, timestamp, flags, _padding, pose,
+                    leftViewMatrix, rightViewMatrix);
+  }
+
   void Clear() { memset(this, 0, sizeof(VRHMDSensorState)); }
 
   bool operator==(const VRHMDSensorState& other) const {
@@ -291,6 +393,13 @@ struct VRFieldOfView {
   double leftDegrees;
 
 #ifdef MOZILLA_INTERNAL_API
+  auto MutTiedFields() {
+    return std::tie(upDegrees, rightDegrees, downDegrees, leftDegrees);
+  }
+
+  bool operator==(const VRFieldOfView& other) const {
+    return TiedFields(*this) == TiedFields(other);
+  }
 
   VRFieldOfView() = default;
   VRFieldOfView(double up, double right, double down, double left)
@@ -306,12 +415,6 @@ struct VRFieldOfView {
     leftDegrees = atan(left) * 180.0 / M_PI;
   }
 
-  bool operator==(const VRFieldOfView& other) const {
-    return other.upDegrees == upDegrees && other.downDegrees == downDegrees &&
-           other.rightDegrees == rightDegrees &&
-           other.leftDegrees == leftDegrees;
-  }
-
   bool operator!=(const VRFieldOfView& other) const {
     return !(*this == other);
   }
@@ -332,10 +435,11 @@ struct VRDisplayState {
 
   // When true, indicates that the VR service has shut down
   bool shutdown;
+  std::array<uint8_t, 3> _padding1;
   // Minimum number of milliseconds to wait before attempting
   // to start the VR service again
   uint32_t minRestartInterval;
-  char displayName[kVRDisplayNameMaxLen];
+  std::array<char, kVRDisplayNameMaxLen> displayName;
   // eight byte character code identifier
   // LSB first, so "ABCDEFGH" -> ('H'<<56) + ('G'<<48) + ('F'<<40) +
   //                             ('E'<<32) + ('D'<<24) + ('C'<<16) +
@@ -343,30 +447,54 @@ struct VRDisplayState {
   uint64_t eightCC;
   VRDisplayCapabilityFlags capabilityFlags;
   VRDisplayBlendMode blendMode;
-  VRFieldOfView eyeFOV[VRDisplayState::NumEyes];
-  Point3D_POD eyeTranslation[VRDisplayState::NumEyes];
+  std::array<uint8_t, 5> _padding2;
+  std::array<VRFieldOfView, VRDisplayState::NumEyes> eyeFOV;
+  static_assert(std::is_pod<VRFieldOfView>::value);
+  std::array<Point3D_POD, VRDisplayState::NumEyes> eyeTranslation;
+  static_assert(std::is_pod<Point3D_POD>::value);
   IntSize_POD eyeResolution;
+  static_assert(std::is_pod<IntSize_POD>::value);
   float nativeFramebufferScaleFactor;
   bool suppressFrames;
   bool isConnected;
   bool isMounted;
+  uint8_t _padding3;
   FloatSize_POD stageSize;
+  static_assert(std::is_pod<FloatSize_POD>::value);
   // We can't use a Matrix4x4 here unless we ensure it's a POD type
-  float sittingToStandingTransform[16];
+  std::array<float, 16> sittingToStandingTransform;
   uint64_t lastSubmittedFrameId;
   bool lastSubmittedFrameSuccessful;
+  std::array<uint8_t, 3> _padding4;
   uint32_t presentingGeneration;
   // Telemetry
   bool reportsDroppedFrames;
+  std::array<uint8_t, 7> _padding5;
   uint64_t droppedFrameCount;
 
 #ifdef MOZILLA_INTERNAL_API
+  auto MutTiedFields() {
+    return std::tie(shutdown, _padding1, minRestartInterval, displayName,
+                    eightCC, capabilityFlags, blendMode, _padding2, eyeFOV,
+                    eyeTranslation, eyeResolution, nativeFramebufferScaleFactor,
+                    suppressFrames, isConnected, isMounted, _padding3,
+                    stageSize, sittingToStandingTransform, lastSubmittedFrameId,
+                    lastSubmittedFrameSuccessful, _padding4,
+                    presentingGeneration, reportsDroppedFrames, _padding5,
+                    droppedFrameCount);
+  }
+
+  bool operator==(const VRDisplayState& other) const {
+    return TiedFields(*this) == TiedFields(other);
+  }
+
   void Clear() { memset(this, 0, sizeof(VRDisplayState)); }
 #endif
 };
+static_assert(std::is_pod<VRDisplayState>::value);
 
 struct VRControllerState {
-  char controllerName[kVRControllerNameMaxLen];
+  std::array<char, kVRControllerNameMaxLen> controllerName;
 #ifdef MOZILLA_INTERNAL_API
   dom::GamepadHand hand;
 #else
@@ -381,6 +509,8 @@ struct VRControllerState {
   // https://immersive-web.github.io/webxr-gamepads-module/#enumdef-gamepadmappingtype
   GamepadMappingType mappingType;
 
+  uint32_t _padding1;
+
   // Start frame ID of the most recent primary select
   // action, or 0 if the select action has never occurred.
   uint64_t selectActionStartFrameId;
@@ -404,12 +534,13 @@ struct VRControllerState {
   uint32_t numButtons;
   uint32_t numAxes;
   uint32_t numHaptics;
+  uint32_t _padding2;
   // The current button pressed bit of button mask.
   uint64_t buttonPressed;
   // The current button touched bit of button mask.
   uint64_t buttonTouched;
-  float triggerValue[kVRControllerMaxButtons];
-  float axisValue[kVRControllerMaxAxis];
+  std::array<float, kVRControllerMaxButtons> triggerValue;
+  std::array<float, kVRControllerMaxAxis> axisValue;
 
 #ifdef MOZILLA_INTERNAL_API
   dom::GamepadCapabilityFlags flags;
@@ -417,6 +548,8 @@ struct VRControllerState {
   ControllerCapabilityFlags flags;
 #endif
 
+  uint16_t _padding3;
+
   // When Cap_Position is set in flags, pose corresponds
   // to the controllers' pose in grip space:
   // https://immersive-web.github.io/webxr/#dom-xrinputsource-gripspace
@@ -429,8 +562,23 @@ struct VRControllerState {
 
   bool isPositionValid;
   bool isOrientationValid;
+  uint16_t _padding4;
 
 #ifdef MOZILLA_INTERNAL_API
+  auto MutTiedFields() {
+    return std::tie(controllerName, hand, type, targetRayMode, mappingType,
+                    _padding1, selectActionStartFrameId,
+                    selectActionStopFrameId, squeezeActionStartFrameId,
+                    squeezeActionStopFrameId, numButtons, numAxes, numHaptics,
+                    _padding2, buttonPressed, buttonTouched, triggerValue,
+                    axisValue, flags, _padding3, pose, targetRayPose,
+                    isPositionValid, isOrientationValid, _padding4);
+  }
+
+  bool operator==(const VRControllerState& other) const {
+    return TiedFields(*this) == TiedFields(other);
+  }
+
   void Clear() { memset(this, 0, sizeof(VRControllerState)); }
 #endif
 };
@@ -532,8 +680,13 @@ struct VRSystemState {
   bool enumerationCompleted;
   VRDisplayState displayState;
   VRHMDSensorState sensorState;
-  VRControllerState controllerState[kVRControllerMaxCount];
+  std::array<VRControllerState, kVRControllerMaxCount> controllerState;
 };
+static_assert(std::is_pod<VRDisplayState>::value);
+static_assert(std::is_pod<VRHMDSensorState>::value);
+static_assert(std::is_pod<VRControllerState>::value);
+
+static_assert(std::is_pod<VRSystemState>::value);
 
 enum class VRFxEventType : uint8_t {
   NONE = 0,
@@ -662,6 +815,11 @@ struct VRExternalShmem {
 
 // As we are memcpy'ing VRExternalShmem and its members around, it must be a POD
 // type
+static_assert(std::is_pod<VRSystemState>::value);
+static_assert(std::is_pod<VRBrowserState>::value);
+static_assert(std::is_pod<VRWindowState>::value);
+static_assert(std::is_pod<VRTelemetryState>::value);
+
 static_assert(std::is_pod<VRExternalShmem>::value,
               "VRExternalShmem must be a POD type.");
 
diff --git a/gfx/vr/gfxVR.cpp b/gfx/vr/gfxVR.cpp
index ed1c6d32d6f6cf9444bd4ec40d190ff476b100fe..6ce16ac5558ad3adc11a4d6247456be14cb27615 100644
--- a/gfx/vr/gfxVR.cpp
+++ b/gfx/vr/gfxVR.cpp
@@ -60,10 +60,11 @@ void VRHMDSensorState::CalcViewMatrices(
   gfx::Matrix4x4 matView =
       matHead * aHeadToEyeTransforms[VRDisplayState::Eye_Left];
   matView.Normalize();
-  memcpy(leftViewMatrix, matView.components, sizeof(matView.components));
+  memcpy(leftViewMatrix.data(), matView.components, sizeof(matView.components));
   matView = matHead * aHeadToEyeTransforms[VRDisplayState::Eye_Right];
   matView.Normalize();
-  memcpy(rightViewMatrix, matView.components, sizeof(matView.components));
+  memcpy(rightViewMatrix.data(), matView.components,
+         sizeof(matView.components));
 }
 
 const IntSize VRDisplayInfo::SuggestedEyeResolution() const {
@@ -85,7 +86,7 @@ const Matrix4x4 VRDisplayInfo::GetSittingToStandingTransform() const {
   Matrix4x4 m;
   // If we could replace Matrix4x4 with a pod type, we could
   // use it directly from the VRDisplayInfo struct.
-  memcpy(m.components, mDisplayState.sittingToStandingTransform,
+  memcpy(m.components, mDisplayState.sittingToStandingTransform.data(),
          sizeof(float) * 16);
   return m;
 }
diff --git a/gfx/vr/gfxVR.h b/gfx/vr/gfxVR.h
index d4d772b6a26e35146277f315ec4a0acba0c8be7d..1f6aa9f26014403ad1f0051e138f2a0d1e6708e2 100644
--- a/gfx/vr/gfxVR.h
+++ b/gfx/vr/gfxVR.h
@@ -14,6 +14,7 @@
 #include "mozilla/RefPtr.h"
 #include "mozilla/gfx/2D.h"
 #include "mozilla/Atomics.h"
+#include "mozilla/dom/TiedFields.h"
 #include "mozilla/EnumeratedArray.h"
 #include "mozilla/TimeStamp.h"
 #include "mozilla/TypedEnumBits.h"
@@ -47,18 +48,31 @@ struct VRDisplayInfo {
   uint32_t mDisplayID;
   uint32_t mPresentingGroups;
   uint32_t mGroupMask;
+  uint32_t _padding;
   uint64_t mFrameId;
   VRDisplayState mDisplayState;
-  VRControllerState mControllerState[kVRControllerMaxCount];
+  std::array<VRControllerState, kVRControllerMaxCount> mControllerState;
+  std::array<VRHMDSensorState, kVRMaxLatencyFrames> mLastSensorState;
+
+  // -
+
+  auto MutTiedFields() {
+    return std::tie(mDisplayID, mPresentingGroups, mGroupMask, _padding,
+                    mFrameId, mDisplayState, mControllerState,
+                    mLastSensorState);
+  }
+
+  // -
 
-  VRHMDSensorState mLastSensorState[kVRMaxLatencyFrames];
   void Clear() { memset(this, 0, sizeof(VRDisplayInfo)); }
   const VRHMDSensorState& GetSensorState() const {
     return mLastSensorState[mFrameId % kVRMaxLatencyFrames];
   }
 
   uint32_t GetDisplayID() const { return mDisplayID; }
-  const char* GetDisplayName() const { return mDisplayState.displayName; }
+  const char* GetDisplayName() const {
+    return mDisplayState.displayName.data();
+  }
   VRDisplayCapabilityFlags GetCapabilities() const {
     return mDisplayState.capabilityFlags;
   }
@@ -77,20 +91,7 @@ struct VRDisplayInfo {
   uint64_t GetFrameId() const { return mFrameId; }
 
   bool operator==(const VRDisplayInfo& other) const {
-    for (size_t i = 0; i < kVRMaxLatencyFrames; i++) {
-      if (mLastSensorState[i] != other.mLastSensorState[i]) {
-        return false;
-      }
-    }
-    // Note that mDisplayState and mControllerState are asserted to be POD
-    // types, so memcmp is safe
-    return mDisplayID == other.mDisplayID &&
-           memcmp(&mDisplayState, &other.mDisplayState,
-                  sizeof(VRDisplayState)) == 0 &&
-           memcmp(mControllerState, other.mControllerState,
-                  sizeof(VRControllerState) * kVRControllerMaxCount) == 0 &&
-           mPresentingGroups == other.mPresentingGroups &&
-           mGroupMask == other.mGroupMask && mFrameId == other.mFrameId;
+    return TiedFields(*this) == TiedFields(other);
   }
 
   bool operator!=(const VRDisplayInfo& other) const {
@@ -115,7 +116,7 @@ struct VRSubmitFrameResultInfo {
 struct VRControllerInfo {
   uint32_t GetControllerID() const { return mControllerID; }
   const char* GetControllerName() const {
-    return mControllerState.controllerName;
+    return mControllerState.controllerName.data();
   }
   dom::GamepadMappingType GetMappingType() const { return mMappingType; }
   uint32_t GetDisplayID() const { return mDisplayID; }
diff --git a/gfx/vr/ipc/VRMessageUtils.h b/gfx/vr/ipc/VRMessageUtils.h
index 0d19c9e4ecff919b3e0070adb410f63523f62a83..18315bdf5be8619a415f9a58d63592aab7a270c7 100644
--- a/gfx/vr/ipc/VRMessageUtils.h
+++ b/gfx/vr/ipc/VRMessageUtils.h
@@ -12,30 +12,56 @@
 #include "mozilla/ArrayUtils.h"
 #include "mozilla/GfxMessageUtils.h"
 #include "mozilla/dom/GamepadMessageUtils.h"
+#include "mozilla/dom/WebGLIpdl.h"
 
 #include "gfxVR.h"
 
 namespace IPC {
 
 template <>
-struct ParamTraits<mozilla::gfx::VRControllerType>
-    : public ContiguousEnumSerializer<mozilla::gfx::VRControllerType,
-                                      mozilla::gfx::VRControllerType::_empty,
-                                      mozilla::gfx::VRControllerType::_end> {};
-
-// VRHMDSensorState is POD, we can use PlainOldDataSerializer
-static_assert(std::is_pod<mozilla::gfx::VRHMDSensorState>::value,
-              "mozilla::gfx::VRHMDSensorState must be a POD type.");
+struct ParamTraits<mozilla::gfx::VRHMDSensorState> final
+    : public ParamTraits_TiedFields<mozilla::gfx::VRHMDSensorState> {};
+template <>
+struct ParamTraits<mozilla::gfx::VRDisplayInfo> final
+    : public ParamTraits_TiedFields<mozilla::gfx::VRDisplayInfo> {};
+template <>
+struct ParamTraits<mozilla::gfx::VRDisplayState> final
+    : public ParamTraits_TiedFields<mozilla::gfx::VRDisplayState> {};
+template <>
+struct ParamTraits<mozilla::gfx::VRControllerState> final
+    : public ParamTraits_TiedFields<mozilla::gfx::VRControllerState> {};
+template <>
+struct ParamTraits<mozilla::gfx::VRFieldOfView> final
+    : public ParamTraits_TiedFields<mozilla::gfx::VRFieldOfView> {};
+template <>
+struct ParamTraits<mozilla::gfx::Point3D_POD> final
+    : public ParamTraits_TiedFields<mozilla::gfx::Point3D_POD> {};
+template <>
+struct ParamTraits<mozilla::gfx::IntSize_POD> final
+    : public ParamTraits_TiedFields<mozilla::gfx::IntSize_POD> {};
 template <>
-struct ParamTraits<mozilla::gfx::VRHMDSensorState>
-    : public PlainOldDataSerializer<mozilla::gfx::VRHMDSensorState> {};
+struct ParamTraits<mozilla::gfx::FloatSize_POD> final
+    : public ParamTraits_TiedFields<mozilla::gfx::FloatSize_POD> {};
+template <>
+struct ParamTraits<mozilla::gfx::VRPose> final
+    : public ParamTraits_TiedFields<mozilla::gfx::VRPose> {};
+
+// -
 
-// VRDisplayInfo is POD, we can use PlainOldDataSerializer
-static_assert(std::is_pod<mozilla::gfx::VRDisplayInfo>::value,
-              "mozilla::gfx::VRDisplayInfo must be a POD type.");
 template <>
-struct ParamTraits<mozilla::gfx::VRDisplayInfo>
-    : public PlainOldDataSerializer<mozilla::gfx::VRDisplayInfo> {};
+struct ParamTraits<mozilla::gfx::VRControllerType>
+    : ParamTraits_IsEnumCase<mozilla::gfx::VRControllerType> {};
+template <>
+struct ParamTraits<mozilla::gfx::TargetRayMode>
+    : ParamTraits_IsEnumCase<mozilla::gfx::TargetRayMode> {};
+template <>
+struct ParamTraits<mozilla::gfx::GamepadMappingType>
+    : ParamTraits_IsEnumCase<mozilla::gfx::GamepadMappingType> {};
+template <>
+struct ParamTraits<mozilla::gfx::VRDisplayBlendMode>
+    : ParamTraits_IsEnumCase<mozilla::gfx::VRDisplayBlendMode> {};
+
+// -
 
 template <>
 struct ParamTraits<mozilla::gfx::VRSubmitFrameResultInfo> {
diff --git a/gfx/vr/moz.build b/gfx/vr/moz.build
index ec56fc56341d82e65803dc34234d8088d5064e56..18f5870fd959d58fdccdb138a6edd796ba7edf7f 100644
--- a/gfx/vr/moz.build
+++ b/gfx/vr/moz.build
@@ -95,6 +95,9 @@ if CONFIG["MOZ_WIDGET_TOOLKIT"] == "gtk":
     CXXFLAGS += CONFIG["MOZ_GTK3_CFLAGS"]
     CFLAGS += CONFIG["MOZ_GTK3_CFLAGS"]
 
+if CONFIG["CC_TYPE"] in ("clang", "clang-cl"):
+    CXXFLAGS += ["-Werror=switch"]
+
 include("/ipc/chromium/chromium-config.mozbuild")
 
 FINAL_LIBRARY = "xul"
diff --git a/gfx/vr/service/OSVRSession.cpp b/gfx/vr/service/OSVRSession.cpp
index 8315bc7f4a18302680f506555701bdd58ce1df13..aeb9606dedede8373f6c036413762ff97c12a715 100644
--- a/gfx/vr/service/OSVRSession.cpp
+++ b/gfx/vr/service/OSVRSession.cpp
@@ -347,7 +347,7 @@ void OSVRSession::InitializeDisplay() {
 
 bool OSVRSession::InitState(mozilla::gfx::VRSystemState& aSystemState) {
   VRDisplayState& state = aSystemState.displayState;
-  strncpy(state.displayName, "OSVR HMD", kVRDisplayNameMaxLen);
+  strncpy(state.displayName.data(), "OSVR HMD", kVRDisplayNameMaxLen);
   state.eightCC = GFX_VR_EIGHTCC('O', 'S', 'V', 'R', ' ', ' ', ' ', ' ');
   state.isConnected = true;
   state.isMounted = false;
diff --git a/gfx/vr/service/OculusSession.cpp b/gfx/vr/service/OculusSession.cpp
index 07b0f208e327a182e1e3c0ba54a5114590ac8eb1..6485c36488e1d6a617a0e5f10ba4bdb86e207695 100644
--- a/gfx/vr/service/OculusSession.cpp
+++ b/gfx/vr/service/OculusSession.cpp
@@ -1010,7 +1010,7 @@ void OculusSession::StopRendering() {
 
 bool OculusSession::InitState(VRSystemState& aSystemState) {
   VRDisplayState& state = aSystemState.displayState;
-  strncpy(state.displayName, "Oculus VR HMD", kVRDisplayNameMaxLen);
+  strncpy(state.displayName.data(), "Oculus VR HMD", kVRDisplayNameMaxLen);
   state.isConnected = true;
   state.isMounted = false;
 
@@ -1147,9 +1147,9 @@ void OculusSession::UpdateEyeParameters(VRSystemState& aState) {
   aState.sensorState.CalcViewMatrices(headToEyeTransforms);
 
   Matrix4x4 matView[2];
-  memcpy(matView[0].components, aState.sensorState.leftViewMatrix,
+  memcpy(matView[0].components, aState.sensorState.leftViewMatrix.data(),
          sizeof(float) * 16);
-  memcpy(matView[1].components, aState.sensorState.rightViewMatrix,
+  memcpy(matView[1].components, aState.sensorState.rightViewMatrix.data(),
          sizeof(float) * 16);
 
   for (uint32_t eye = 0; eye < VRDisplayState::NumEyes; eye++) {
@@ -1329,8 +1329,9 @@ void OculusSession::EnumerateControllers(VRSystemState& aState,
       // Touch Controller detected
       if (controllerState.controllerName[0] == '\0') {
         // Controller has been just enumerated
-        strncpy(controllerState.controllerName, OculusControllerNames[handIdx],
-                kVRControllerNameMaxLen);
+        strncpy(controllerState.controllerName.data(),
+                OculusControllerNames[handIdx],
+                controllerState.controllerName.size());
         controllerState.hand = OculusControllerHand[handIdx];
         controllerState.targetRayMode = gfx::TargetRayMode::TrackedPointer;
         controllerState.numButtons = kNumOculusButtons;
diff --git a/gfx/vr/service/OpenVRSession.cpp b/gfx/vr/service/OpenVRSession.cpp
index bfb486799db342222bdaa09dce7da5f8cd4317d4..9f5d332b4eb0661b4eed4ffbe6190acdd612a3b1 100644
--- a/gfx/vr/service/OpenVRSession.cpp
+++ b/gfx/vr/service/OpenVRSession.cpp
@@ -697,7 +697,7 @@ void OpenVRSession::Shutdown() {
 
 bool OpenVRSession::InitState(VRSystemState& aSystemState) {
   VRDisplayState& state = aSystemState.displayState;
-  strncpy(state.displayName, "OpenVR HMD", kVRDisplayNameMaxLen);
+  strncpy(state.displayName.data(), "OpenVR HMD", kVRDisplayNameMaxLen);
   state.eightCC = GFX_VR_EIGHTCC('O', 'p', 'e', 'n', 'V', 'R', ' ', ' ');
   state.isConnected =
       mVRSystem->IsTrackedDeviceConnected(::vr::k_unTrackedDeviceIndex_Hmd);
@@ -987,8 +987,8 @@ void OpenVRSession::EnumerateControllers(VRSystemState& aState) {
         MOZ_ASSERT(controllerType == contrlType ||
                    controllerType == VRControllerType::_empty);
         controllerType = contrlType;
-        strncpy(controllerState.controllerName, deviceId.BeginReading(),
-                kVRControllerNameMaxLen);
+        strncpy(controllerState.controllerName.data(), deviceId.BeginReading(),
+                controllerState.controllerName.size());
         controllerState.numHaptics = kNumOpenVRHaptics;
         controllerState.targetRayMode = gfx::TargetRayMode::TrackedPointer;
         controllerState.type = controllerType;
diff --git a/ipc/chromium/src/base/pickle.cc b/ipc/chromium/src/base/pickle.cc
index fa72edca2085d20fb05b138765cf7868d97bc57e..21d643b233a53ee54253a8c54f7515f4dc742ca2 100644
--- a/ipc/chromium/src/base/pickle.cc
+++ b/ipc/chromium/src/base/pickle.cc
@@ -96,6 +96,7 @@ void PickleIterator::CopyInto(T* dest) {
          (MOZ_ALIGNOF(T) <=
           sizeof(Pickle::memberAlignmentType))>::Copy(dest, iter_.Data());
 }
+template void PickleIterator::CopyInto<char>(char*);
 
 bool Pickle::IteratorHasRoomFor(const PickleIterator& iter,
                                 uint32_t len) const {
diff --git a/ipc/chromium/src/base/pickle.h b/ipc/chromium/src/base/pickle.h
index 5f648808486394b72aa365f603cd8c994fdcbb52..45779546108e3f13e00b9e0d7d3bb584d16ed875 100644
--- a/ipc/chromium/src/base/pickle.h
+++ b/ipc/chromium/src/base/pickle.h
@@ -128,6 +128,9 @@ class Pickle {
 
   template <class T>
   [[nodiscard]] bool ReadScalar(PickleIterator* iter, T* result) const {
+    static_assert(std::is_arithmetic<T>::value);
+    static_assert(!std::is_same<typename std::remove_cv<T>::type, bool>::value);
+
     DCHECK(iter);
 
     if (!IteratorHasRoomFor(*iter, sizeof(*result)))
@@ -168,6 +171,15 @@ class Pickle {
   // appended to the end of the Pickle's payload.  When reading values from a
   // Pickle, it is important to read them in the order in which they were added
   // to the Pickle.
+  bool WriteBytes(const void* data, uint32_t data_len);
+
+  template <class T>
+  bool WriteScalar(const T& value) {
+    static_assert(std::is_arithmetic<T>::value);
+    static_assert(!std::is_same<typename std::remove_cv<T>::type, bool>::value);
+    return WriteBytes(&value, sizeof(value));
+  }
+
   bool WriteBool(bool value);
   bool WriteInt16(int16_t value);
   bool WriteUInt16(uint16_t value);
@@ -184,7 +196,7 @@ class Pickle {
   bool WriteString(const std::string& value);
   bool WriteWString(const std::wstring& value);
   bool WriteData(const char* data, uint32_t length);
-  bool WriteBytes(const void* data, uint32_t data_len);
+
   // Takes ownership of data
   bool WriteBytesZeroCopy(void* data, uint32_t data_len, uint32_t capacity);
 
diff --git a/ipc/chromium/src/chrome/common/ipc_message_utils.h b/ipc/chromium/src/chrome/common/ipc_message_utils.h
index a2a38becefdcb909301aa30d074df3396df46e73..ec6b23eacbd86fae043a7f6db5c33d3068ee23a5 100644
--- a/ipc/chromium/src/chrome/common/ipc_message_utils.h
+++ b/ipc/chromium/src/chrome/common/ipc_message_utils.h
@@ -82,6 +82,11 @@ class MOZ_STACK_CLASS MessageWriter final {
 
 #undef FORWARD_WRITE
 
+  template <class T>
+  bool WriteScalar(const T& result) {
+    return message_.WriteScalar(result);
+  }
+
   bool WriteData(const char* data, uint32_t length) {
     return message_.WriteData(data, length);
   }
@@ -167,6 +172,11 @@ class MOZ_STACK_CLASS MessageReader final {
 
 #undef FORWARD_READ
 
+  template <class T>
+  [[nodiscard]] bool ReadScalar(T* const result) {
+    return message_.ReadScalar(&iter_, result);
+  }
+
   [[nodiscard]] bool ReadBytesInto(void* data, uint32_t length) {
     return message_.ReadBytesInto(&iter_, data, length);
   }
@@ -720,6 +730,17 @@ struct ParamTraitsFundamental<bool> {
   }
 };
 
+template <>
+struct ParamTraitsFundamental<char> {
+  typedef char param_type;
+  static void Write(MessageWriter* writer, const param_type& p) {
+    writer->WriteScalar(p);
+  }
+  static bool Read(MessageReader* reader, param_type* r) {
+    return reader->ReadScalar(r);
+  }
+};
+
 template <>
 struct ParamTraitsFundamental<int> {
   typedef int param_type;
@@ -791,6 +812,28 @@ struct ParamTraitsFundamental<double> {
 template <class P>
 struct ParamTraitsFixed : ParamTraitsFundamental<P> {};
 
+template <>
+struct ParamTraitsFixed<int8_t> {
+  typedef int8_t param_type;
+  static void Write(MessageWriter* writer, const param_type& p) {
+    writer->WriteScalar(p);
+  }
+  static bool Read(MessageReader* reader, param_type* r) {
+    return reader->ReadScalar(r);
+  }
+};
+
+template <>
+struct ParamTraitsFixed<uint8_t> {
+  typedef uint8_t param_type;
+  static void Write(MessageWriter* writer, const param_type& p) {
+    writer->WriteScalar(p);
+  }
+  static bool Read(MessageReader* reader, param_type* r) {
+    return reader->ReadScalar(r);
+  }
+};
+
 template <>
 struct ParamTraitsFixed<int16_t> {
   typedef int16_t param_type;