Backed out 4 changesets (bug 1658419) for causing failures at test_check_timestamp.html. CLOSED TREE

Backed out changeset d2688bd29cba (bug 1658419)
Backed out changeset 7db6f9ba1fe6 (bug 1658419)
Backed out changeset 8d3c6d538b7b (bug 1658419)
Backed out changeset 11e3f0658049 (bug 1658419)
This commit is contained in:
Butkovits Atila 2021-03-04 00:13:56 +02:00
parent 68a3be8287
commit 7846407933
32 changed files with 32 additions and 2787 deletions

View File

@ -37,7 +37,6 @@ namespace mozilla::dom {
class GamepadPlatformService;
class GamepadServiceTest;
class GamepadTestHelper;
class XRInputSource;
// The "kind" of a gamepad handle is based on which provider created it
@ -74,7 +73,6 @@ class GamepadHandle {
// create new handles and inspect their actual value
friend class mozilla::dom::GamepadPlatformService;
friend class mozilla::dom::GamepadServiceTest;
friend class mozilla::dom::GamepadTestHelper;
friend class mozilla::dom::XRInputSource;
friend class mozilla::gfx::VRDisplayClient;
friend class mozilla::gfx::VRManager;

View File

@ -108,10 +108,6 @@ void GamepadManager::StopMonitoring() {
void GamepadManager::BeginShutdown() {
mShuttingDown = true;
StopMonitoring();
if (mMaybeGamepadStateReceiver) {
mMaybeGamepadStateReceiver->StopMonitoringThread();
mMaybeGamepadStateReceiver = Nothing{};
}
// Don't let windows call back to unregister during shutdown
for (uint32_t i = 0; i < mListeners.Length(); i++) {
mListeners[i]->SetHasGamepadEventListener(false);
@ -665,22 +661,4 @@ already_AddRefed<Promise> GamepadManager::SetLightIndicatorColor(
++mPromiseID;
return promise.forget();
}
void GamepadManager::SetupRemoteInfo(
const GamepadStateBroadcastReceiverInfo& aReceiverInfo) {
mMaybeGamepadStateReceiver = GamepadStateReceiver::Create(aReceiverInfo);
if (mMaybeGamepadStateReceiver) {
RefPtr<GamepadManager> thisRefPtr(this);
bool threadStarted = mMaybeGamepadStateReceiver->StartMonitoringThread(
[thisRefPtr](const GamepadChangeEvent& e) {
NS_DispatchToMainThread(NS_NewRunnableFunction(
"GamepadStateReceiver::MonitoringThread",
[thisRefPtr, e] { thisRefPtr->Update(e); }));
});
if (!threadStarted) {
mMaybeGamepadStateReceiver = Nothing{};
}
}
}
} // namespace mozilla::dom

View File

@ -12,7 +12,6 @@
// Needed for GamepadMappingType
#include "mozilla/dom/GamepadBinding.h"
#include "mozilla/dom/GamepadHandle.h"
#include "mozilla/dom/GamepadStateReceiver.h"
#include <utility>
class nsGlobalWindowInner;
@ -84,8 +83,6 @@ class GamepadManager final : public nsIObserver {
nsIGlobalObject* aGlobal,
ErrorResult& aRv);
void SetupRemoteInfo(const GamepadStateBroadcastReceiverInfo& aReceiverInfo);
protected:
GamepadManager();
~GamepadManager() = default;
@ -151,8 +148,6 @@ class GamepadManager final : public nsIObserver {
// has been sent to that window.
nsTArray<RefPtr<nsGlobalWindowInner>> mListeners;
uint32_t mPromiseID;
Maybe<GamepadStateReceiver> mMaybeGamepadStateReceiver;
};
} // namespace dom

View File

@ -8,7 +8,6 @@
#include "mozilla/dom/GamepadEventChannelParent.h"
#include "mozilla/dom/GamepadMonitoring.h"
#include "mozilla/dom/GamepadStateBroadcastReceiverInfo.h"
#include "mozilla/dom/GamepadTestChannelParent.h"
#include "mozilla/ipc/BackgroundParent.h"
#include "mozilla/Mutex.h"
@ -80,13 +79,9 @@ void GamepadMonitoringState::Set(bool aIsMonitoring) {
}
}
// Note - If GamepadStateBroadcaster::Create() fails (either because the
// platform doesn't support it, or if something unexpected goes wrong),
// everything in this class will silently fall back to using IPDL
GamepadPlatformService::GamepadPlatformService()
: mNextGamepadHandleValue(1),
mMutex("mozilla::dom::GamepadPlatformService"),
mMaybeGamepadStateBroadcaster(GamepadStateBroadcaster::Create()) {}
mMutex("mozilla::dom::GamepadPlatformService") {}
GamepadPlatformService::~GamepadPlatformService() { Cleanup(); }
@ -115,12 +110,6 @@ void GamepadPlatformService::NotifyGamepadChange(GamepadHandle aHandle,
MOZ_ASSERT(XRE_IsParentProcess());
MOZ_ASSERT(!NS_IsMainThread());
// This function (which uses IPDL) should only ever be used if there
// is no GamepadStateBroadcaster, either because the platform doesn't
// support it (currently only Windows does), or-else because it failed to
// initialize for whatever reason.
MOZ_ASSERT(!mMaybeGamepadStateBroadcaster);
GamepadChangeEventBody body(aInfo);
GamepadChangeEvent e(aHandle, body);
@ -145,20 +134,13 @@ GamepadHandle GamepadPlatformService::AddGamepad(
GamepadHandle gamepadHandle{mNextGamepadHandleValue++,
GamepadHandleKind::GamepadPlatformManager};
if (mMaybeGamepadStateBroadcaster) {
mMaybeGamepadStateBroadcaster->AddGamepad(
gamepadHandle, aID, aMapping, aHand, aNumButtons, aNumAxes, aHaptics,
aNumLightIndicator, aNumTouchEvents);
} else {
// Only VR controllers has displayID, we give 0 to the general gamepads.
GamepadAdded a(NS_ConvertUTF8toUTF16(nsDependentCString(aID)), aMapping,
aHand, 0, aNumButtons, aNumAxes, aHaptics,
aNumLightIndicator, aNumTouchEvents);
mGamepadAdded.emplace(gamepadHandle, a);
NotifyGamepadChange<GamepadAdded>(gamepadHandle, a);
}
// Only VR controllers has displayID, we give 0 to the general gamepads.
GamepadAdded a(NS_ConvertUTF8toUTF16(nsDependentCString(aID)), aMapping,
aHand, 0, aNumButtons, aNumAxes, aHaptics, aNumLightIndicator,
aNumTouchEvents);
mGamepadAdded.emplace(gamepadHandle, a);
NotifyGamepadChange<GamepadAdded>(gamepadHandle, a);
return gamepadHandle;
}
@ -167,14 +149,9 @@ void GamepadPlatformService::RemoveGamepad(GamepadHandle aHandle) {
// platform-dependent backends
MOZ_ASSERT(XRE_IsParentProcess());
MOZ_ASSERT(!NS_IsMainThread());
if (mMaybeGamepadStateBroadcaster) {
mMaybeGamepadStateBroadcaster->RemoveGamepad(aHandle);
} else {
GamepadRemoved a;
NotifyGamepadChange<GamepadRemoved>(aHandle, a);
mGamepadAdded.erase(aHandle);
}
GamepadRemoved a;
NotifyGamepadChange<GamepadRemoved>(aHandle, a);
mGamepadAdded.erase(aHandle);
}
void GamepadPlatformService::NewButtonEvent(GamepadHandle aHandle,
@ -184,14 +161,8 @@ void GamepadPlatformService::NewButtonEvent(GamepadHandle aHandle,
// platform-dependent backends
MOZ_ASSERT(XRE_IsParentProcess());
MOZ_ASSERT(!NS_IsMainThread());
if (mMaybeGamepadStateBroadcaster) {
mMaybeGamepadStateBroadcaster->NewButtonEvent(aHandle, aButton, aPressed,
aTouched, aValue);
} else {
GamepadButtonInformation a(aButton, aValue, aPressed, aTouched);
NotifyGamepadChange<GamepadButtonInformation>(aHandle, a);
}
GamepadButtonInformation a(aButton, aValue, aPressed, aTouched);
NotifyGamepadChange<GamepadButtonInformation>(aHandle, a);
}
void GamepadPlatformService::NewButtonEvent(GamepadHandle aHandle,
@ -232,12 +203,8 @@ void GamepadPlatformService::NewAxisMoveEvent(GamepadHandle aHandle,
// platform-dependent backends
MOZ_ASSERT(XRE_IsParentProcess());
MOZ_ASSERT(!NS_IsMainThread());
if (mMaybeGamepadStateBroadcaster) {
mMaybeGamepadStateBroadcaster->NewAxisMoveEvent(aHandle, aAxis, aValue);
} else {
GamepadAxisInformation a(aAxis, aValue);
NotifyGamepadChange<GamepadAxisInformation>(aHandle, a);
}
GamepadAxisInformation a(aAxis, aValue);
NotifyGamepadChange<GamepadAxisInformation>(aHandle, a);
}
void GamepadPlatformService::NewLightIndicatorTypeEvent(
@ -246,13 +213,8 @@ void GamepadPlatformService::NewLightIndicatorTypeEvent(
// platform-dependent backends
MOZ_ASSERT(XRE_IsParentProcess());
MOZ_ASSERT(!NS_IsMainThread());
if (mMaybeGamepadStateBroadcaster) {
mMaybeGamepadStateBroadcaster->NewLightIndicatorTypeEvent(aHandle, aLight,
aType);
} else {
GamepadLightIndicatorTypeInformation a(aLight, aType);
NotifyGamepadChange<GamepadLightIndicatorTypeInformation>(aHandle, a);
}
GamepadLightIndicatorTypeInformation a(aLight, aType);
NotifyGamepadChange<GamepadLightIndicatorTypeInformation>(aHandle, a);
}
void GamepadPlatformService::NewPoseEvent(GamepadHandle aHandle,
@ -261,13 +223,8 @@ void GamepadPlatformService::NewPoseEvent(GamepadHandle aHandle,
// platform-dependent backends
MOZ_ASSERT(XRE_IsParentProcess());
MOZ_ASSERT(!NS_IsMainThread());
if (mMaybeGamepadStateBroadcaster) {
mMaybeGamepadStateBroadcaster->NewPoseEvent(aHandle, aState);
} else {
GamepadPoseInformation a(aState);
NotifyGamepadChange<GamepadPoseInformation>(aHandle, a);
}
GamepadPoseInformation a(aState);
NotifyGamepadChange<GamepadPoseInformation>(aHandle, a);
}
void GamepadPlatformService::NewMultiTouchEvent(
@ -278,13 +235,8 @@ void GamepadPlatformService::NewMultiTouchEvent(
MOZ_ASSERT(XRE_IsParentProcess());
MOZ_ASSERT(!NS_IsMainThread());
if (mMaybeGamepadStateBroadcaster) {
mMaybeGamepadStateBroadcaster->NewMultiTouchEvent(aHandle, aTouchArrayIndex,
aState);
} else {
GamepadTouchInformation a(aTouchArrayIndex, aState);
NotifyGamepadChange<GamepadTouchInformation>(aHandle, a);
}
GamepadTouchInformation a(aTouchArrayIndex, aState);
NotifyGamepadChange<GamepadTouchInformation>(aHandle, a);
}
void GamepadPlatformService::ResetGamepadIndexes() {
@ -303,15 +255,6 @@ void GamepadPlatformService::AddChannelParent(
MOZ_ASSERT(aParent);
MOZ_ASSERT(!mChannelParents.Contains(aParent));
// If we have working shared memory, attempt to set that up with the
// remote process
GamepadStateBroadcastReceiverInfo receiverInfo{};
if (mMaybeGamepadStateBroadcaster &&
mMaybeGamepadStateBroadcaster->AddReceiverAndGenerateRemoteInfo(
aParent, &receiverInfo)) {
Unused << aParent->SendSetupSharedMemory(receiverInfo);
}
// We use mutex here to prevent race condition with monitor thread
{
MutexAutoLock autoLock(mMutex);
@ -319,13 +262,11 @@ void GamepadPlatformService::AddChannelParent(
// For a new GamepadEventChannel, we have to send the exising GamepadAdded
// to it to make it can have the same amount of gamepads with others.
if (!mMaybeGamepadStateBroadcaster) {
if (mChannelParents.Length() > 1) {
for (const auto& evt : mGamepadAdded) {
GamepadChangeEventBody body(evt.second);
GamepadChangeEvent e(evt.first, body);
aParent->DispatchUpdateEvent(e);
}
if (mChannelParents.Length() > 1) {
for (const auto& evt : mGamepadAdded) {
GamepadChangeEventBody body(evt.second);
GamepadChangeEvent e(evt.first, body);
aParent->DispatchUpdateEvent(e);
}
}
}
@ -355,11 +296,6 @@ void GamepadPlatformService::RemoveChannelParent(
GamepadMonitoringState::GetSingleton().Set(false);
StopGamepadMonitoring();
if (mMaybeGamepadStateBroadcaster) {
mMaybeGamepadStateBroadcaster->RemoveReceiver(aParent);
}
ResetGamepadIndexes();
MaybeShutdown();
}

View File

@ -9,7 +9,6 @@
#include "mozilla/dom/GamepadBinding.h"
#include "mozilla/dom/GamepadHandle.h"
#include "mozilla/dom/GamepadStateBroadcaster.h"
#include <map>
#include "mozilla/Mutex.h"
@ -145,10 +144,6 @@ class GamepadPlatformService final {
Mutex mMutex;
std::map<GamepadHandle, GamepadAdded> mGamepadAdded;
// This variable may contain the shared-memory "GamepadStateBroadcaster" if
// it is available. If not, everything will just fall back to using IPDL
Maybe<GamepadStateBroadcaster> mMaybeGamepadStateBroadcaster;
};
} // namespace dom

View File

@ -1,149 +0,0 @@
/* -*- 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/. */
// This is the receiving half of a pair. See GamepadStateReceiver.h for the
// receiving half of this.
// Implements single-broadcaster, multi-receiver shared memory for the gamepad
// system. Allows the gamepad monitor thread in the main process to directly
// update the shared memory to the latest state and trigger an event in all
// the content processes, causing them to read the shared memory and pass it
// to Javascript.
// This takes advantage of the fact that gamepad clients generally only care
// about the most recent state of the gamepad, not the stuff that happened
// while they weren't looking.
// This currently only does anything interesting on Windows, and it uses
// Pimpl idiom to avoid OS-specific types in this header.
#ifndef DOM_GAMEPAD_GAMEPADSTATEBROADCASTER_H_
#define DOM_GAMEPAD_GAMEPADSTATEBROADCASTER_H_
#include "mozilla/dom/GamepadBinding.h"
#include "mozilla/dom/GamepadHandle.h"
#include "mozilla/dom/GamepadLightIndicatorBinding.h"
#include "mozilla/dom/GamepadPoseState.h"
#include "mozilla/dom/GamepadTouchState.h"
#include "mozilla/Maybe.h"
#include "mozilla/UniquePtr.h"
namespace mozilla::ipc {
class IProtocol;
} // namespace mozilla::ipc
namespace mozilla::dom {
class GamepadChangeEvent;
class GamepadTestHelper;
// IPDL structure that knows how to initialize a receiver
class GamepadStateBroadcastReceiverInfo;
// The broadcasting half of the pair
//
// Can be used to deliver an event quickly and simultaneously to every
// receiver. Does not guarantee that every event will be seen by every
// receiver, but *does* guarantee that every receiver will see a coherent
// snapshot of the controller state at a point in time.
//
// (IE they will never see an unplugged controller with a button pressed)
//
// # Example:
//
// Maybe<GamepadStateBroadcaster> maybeBroadcaster =
// GamepadStateBroadcaster::Create();
// MOZ_ASSERT(maybeBroadcaster);
//
// GamepadStateBroadcastReceiverInfo remoteInfo{};
// bool result = maybeBroadcaster->AddReceiverAndGenerateRemoteInfo(
// aIPCActor, &remoteInfo);
// MOZ_ASSERT(result);
//
// result = aIPCActor->SendBroadcasterInfo(remoteInfo);
// MOZ_ASSERT(result);
//
// maybeBroadcaster->AddGamepad(aHandle);
// maybeBroadcaster->NewAxisMoveEvent(aHandle, 0 /*axisId*/, 0.0 /*value*/);
//
class GamepadStateBroadcaster {
public:
// Create a new broadcaster
//
// Allocates the shared memory region for a new broadcaster that can
// be shared with others using `AddReceiverAndGenerateRemoteInfo()`
static Maybe<GamepadStateBroadcaster> Create();
// Add an IPDL actor as a new receiver and create remote info
//
// `aActor` will be added as a listener for events, and `aOut` will contain
// the remote info that must be passed via IPDL to the remote process.
//
// The info will only be valid within the context of `aActor`, and failure
// to deliver it will result in a handle leak until the remote actor's
// process dies.
bool AddReceiverAndGenerateRemoteInfo(
const mozilla::ipc::IProtocol* aActor,
GamepadStateBroadcastReceiverInfo* aOut);
// Removes an IPDL actor that was previously registered as a receiver
//
// This will free up any system resources that were required on the
// broadcaster side.
void RemoveReceiver(const mozilla::ipc::IProtocol* aActor);
// Adds a new gamepad to shared memory region
void AddGamepad(GamepadHandle aHandle, const char* aID,
GamepadMappingType aMapping, GamepadHand aHand,
uint32_t aNumButtons, uint32_t aNumAxes, uint32_t aNumHaptics,
uint32_t aNumLights, uint32_t aNumTouches);
// Removes a gamepad from the shared memory region
void RemoveGamepad(GamepadHandle aHandle);
// Update a gamepad axis in the shared memory region
void NewAxisMoveEvent(GamepadHandle aHandle, uint32_t aAxis, double aValue);
// Update a gamepad button in the shared memory region
void NewButtonEvent(GamepadHandle aHandle, uint32_t aButton, bool aPressed,
bool aTouched, double aValue);
// Update a gamepad light indicator in the shared memory region
void NewLightIndicatorTypeEvent(GamepadHandle aHandle, uint32_t aLight,
GamepadLightIndicatorType aType);
// Update a gamepad pose state in the shared memory region
void NewPoseEvent(GamepadHandle aHandle, const GamepadPoseState& aState);
// Update a gamepad multi-touch state in the shared memory region
void NewMultiTouchEvent(GamepadHandle aHandle, uint32_t aTouchArrayIndex,
const GamepadTouchState& aState);
// Allow move
GamepadStateBroadcaster(GamepadStateBroadcaster&& aOther);
GamepadStateBroadcaster& operator=(GamepadStateBroadcaster&& aOther);
// Disallow copy
GamepadStateBroadcaster(const GamepadStateBroadcaster&) = delete;
GamepadStateBroadcaster& operator=(const GamepadStateBroadcaster&) = delete;
~GamepadStateBroadcaster();
private:
class Impl;
GamepadStateBroadcaster();
explicit GamepadStateBroadcaster(UniquePtr<Impl> aImpl);
UniquePtr<Impl> mImpl;
friend class GamepadTestHelper;
void SendTestCommand(uint32_t aCommandId);
};
} // namespace mozilla::dom
#endif // DOM_GAMEPAD_GAMEPADSTATEBROADCASTER_H_

View File

@ -1,121 +0,0 @@
/* -*- 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/. */
// This is the receiving half of a pair. See GamepadStateBroadcaster.h for the
// transmitting half of this.
// Implements single-broadcaster, multi-receiver shared memory for the gamepad
// system. Allows the gamepad monitor thread in the main process to directly
// update the shared memory to the latest state and trigger an event in all
// the content processes, causing them to read the shared memory and pass it
// to Javascript.
// This takes advantage of the fact that gamepad clients generally only care
// about the most recent state of the gamepad, not the stuff that happened
// while they weren't looking.
// This currently only does anything interesting on Windows, and it uses
// Pimpl idiom to avoid OS-specific types in this header.
#ifndef DOM_GAMEPAD_GAMEPADSTATERECEIVER_H_
#define DOM_GAMEPAD_GAMEPADSTATERECEIVER_H_
#include "mozilla/dom/GamepadBinding.h"
#include "mozilla/dom/GamepadHandle.h"
#include "mozilla/dom/GamepadLightIndicatorBinding.h"
#include "mozilla/dom/GamepadPoseState.h"
#include "mozilla/dom/GamepadTouchState.h"
#include "mozilla/Maybe.h"
#include "mozilla/UniquePtr.h"
#include <functional>
namespace mozilla::ipc {
class IProtocol;
} // namespace mozilla::ipc
namespace mozilla::dom {
class GamepadChangeEvent;
class GamepadTestHelper;
// IPDL structure that knows how to initialize a receiver
class GamepadStateBroadcastReceiverInfo;
// The receiving half of the pair
//
// Spins up a thread that waits for changes to the shared memory to arrive
// from the other side, and generates GamepadChangeEvents that can be used to
// signal Javascript that the gamepad state has changed
//
// # Example
//
// mozilla::ipc::IPCResult RecvGamepadReceiverInfo(
// const GamepadStateBroadcastReceiverInfo& aReceiverInfo) {
// Maybe<GamepadStateReceiver> maybeReceiver =
// GamepadStateReceiver::Create(aReceiverInfo);
// MOZ_ASSERT(maybeReceiver);
//
// // This thread will probably be running async, so be sure to capture by
// // value and not reference on most things
// maybeReceiver->StartMonitoringThread([=](const GamepadChangeEvent& e) {
// this->handleEvent(e);
// });
// }
//
class GamepadStateReceiver {
public:
// Create the receiving side from info passed over IPDL
//
// The GamepadStateBroadcastReceiverInfo structure should have been obtained
// from a call to `AddReceiverAndGenerateRemoteInfo()` and passed via IPDL
// to this function in the remote process.
static Maybe<GamepadStateReceiver> Create(
const GamepadStateBroadcastReceiverInfo& aReceiverInfo);
// Start a thread that monitors the shared memory for changes
//
// The thread will call `aFn` with an event generated for each gamepad
// value that has changed since the last time it checked. Note that this
// means that multiple changes to the same value will appear as a single
// change if they happen quickly enough. This generally doesn't matter for
// gamepads.
bool StartMonitoringThread(
const std::function<void(const GamepadChangeEvent&)>& aFn);
// Stop the monitoring thread
//
// If the broadcaster continues sending messages after this is called, they
// will be missed. It is okay to restart the monitoring thread and even
// to pass in a different function.
void StopMonitoringThread();
// Allow move
GamepadStateReceiver(GamepadStateReceiver&& aOther);
GamepadStateReceiver& operator=(GamepadStateReceiver&& aOther);
// Disallow copy
GamepadStateReceiver(const GamepadStateReceiver&) = delete;
GamepadStateReceiver& operator=(const GamepadStateReceiver&) = delete;
~GamepadStateReceiver();
private:
class Impl;
GamepadStateReceiver();
explicit GamepadStateReceiver(UniquePtr<Impl> aImpl);
UniquePtr<Impl> mImpl;
friend class GamepadTestHelper;
bool StartMonitoringThreadForTesting(
const std::function<void(const GamepadChangeEvent&)>& aMonitorFn,
const std::function<void(uint32_t)>& aTestCommandFn);
};
} // namespace mozilla::dom
#endif // DOM_GAMEPAD_GAMEPADSTATERECEIVER_H_

View File

@ -1,295 +0,0 @@
/* -*- 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/. */
/* Implements a piece of cross-process shared memory that can be shared with
* many processes simultaniously, and is synchronized by a cross-process mutex.
*
* In almost all cases, IPDL and its machinery should be preferred to this.
* This class should only be used if you really know you need it, and
* precautions need to be taken to avoid introducing security bugs.
*
* Specifically:
* - Don't assume that any process other than the main process is trustworthy,
* since exploits may exist in graphics drivers, video codecs, etc.
* - Don't assume the other side can't modify the shared memory even while
* you hold the lock.
* - Don't assume that if you read the same piece of data twice, that it will
* return the same value.
* - Don't assume the other side will ever actually release the lock.
* IE using RunWithLock() on the main thread is probably a bad idea.
*
* If possible, design things so that the trusted side is on a separate
* thread and is write-only and you avoid those problems altogether. If you,
* must read, consider taking a "snapshot" of the shared memory by copying it
* to a local variable, operating on that, and then copying it back to shared
* memory when done.
*
*/
#ifndef DOM_GAMEPAD_SYNCHRONIZEDSHAREDMEMORY_H_
#define DOM_GAMEPAD_SYNCHRONIZEDSHAREDMEMORY_H_
#include "mozilla/Maybe.h"
#include "mozilla/UniquePtr.h"
#include <cinttypes>
#include <type_traits>
namespace mozilla::ipc {
class IProtocol;
} // namespace mozilla::ipc
namespace mozilla::dom {
// IPDL structure that knows how to serialize this over IPC
class SynchronizedSharedMemoryRemoteInfo;
// Details that you don't need to know to use this class
//
// This mostly represents an interface/implementation firewall (Pimpl Idiom)
// because the implementation is per-platform and we want to keep this file
// free of platform-specific headers.
//
// It is implemented in terms of "void*" because template classes with template
// methods can't hide their implementations in .cpp files. The main class is a
// template wrapper for this that exposes it as a typesafe API for type `T`.
//
// See SynchronizedSharedMemory below for usage of the public API
class SynchronizedSharedMemoryDetail {
public:
static Maybe<SynchronizedSharedMemoryDetail> CreateNew(uint32_t aSize);
~SynchronizedSharedMemoryDetail();
void LockMutex();
void UnlockMutex();
void* GetPtr() const;
// Allows this to be transferred across IPDL
static Maybe<SynchronizedSharedMemoryDetail> CreateFromRemoteInfo(
const SynchronizedSharedMemoryRemoteInfo& aIPCInfo);
bool GenerateRemoteInfo(const mozilla::ipc::IProtocol* aActor,
SynchronizedSharedMemoryRemoteInfo* aOut);
// Allow move
SynchronizedSharedMemoryDetail(SynchronizedSharedMemoryDetail&&);
SynchronizedSharedMemoryDetail& operator=(SynchronizedSharedMemoryDetail&&);
// Disallow copy
SynchronizedSharedMemoryDetail(const SynchronizedSharedMemoryDetail&) =
delete;
SynchronizedSharedMemoryDetail& operator=(
const SynchronizedSharedMemoryDetail&) = delete;
private:
class Impl;
SynchronizedSharedMemoryDetail();
explicit SynchronizedSharedMemoryDetail(UniquePtr<Impl> aImpl);
UniquePtr<Impl> mImpl;
// Testing-related helper
template <typename T>
friend class SynchronizedSharedMemory;
bool GenerateTestRemoteInfo(SynchronizedSharedMemoryRemoteInfo* aOut);
};
// Represents a piece of cross-process synchronized shared memory
//
// Can create a piece of shared memory to hold an object of type T,
// pass it over IPDL to another actor, and can perform synchronized
// cross-process access of the object
//
// See top of file for warnings about security.
//
// The type, T, must be TriviallyCopyable since sharing a piece of memory
// between 2 processes is akin to memcopy-ing its bytes from one program to
// another. StandardLayout may not be strictly necessary, but conforming to the
// platform C ABI is probably a good idea when sharing across processes.
//
// Although not statically enforcable, generally the object should not contain
// pointers, since they are usually meaningless across processes.
template <typename T>
class SynchronizedSharedMemory {
static_assert(std::is_trivially_copyable<T>::value,
"SynchronizedSharedMemory can only be used with types that are "
"trivially copyable");
static_assert(std::is_standard_layout<T>::value,
"SynchronizedSharedMemory can only be used with types that are "
"standard layout");
public:
// Construct a new object of type T in cross-process shareable memory
//
// Returns `Nothing` if an error occurs
//
// # Example
// ```
// struct MyObject {
// int i;
// };
// auto maybeSharedMemory = SynchronizedSharedMemory<MyObject>::CreateNew();
// MOZ_ASSERT(maybeSharedMemory);
// ```
template <typename... Args>
static Maybe<SynchronizedSharedMemory> CreateNew(Args&&... args) {
auto maybeDetail = SynchronizedSharedMemoryDetail::CreateNew(sizeof(T));
if (!maybeDetail) {
return Nothing{};
}
new ((*maybeDetail).GetPtr()) T(std::forward<Args>(args)...);
return Some(SynchronizedSharedMemory(std::move(*maybeDetail)));
}
// Run the given function on the current thread while holding a lock on the
// cross-process mutex
//
// Guarantees that no well-behaved users** will race or forget to unlock when
// accessing this object, even from other processes.
//
// ** Of course, proper locking/unlocking can never be enforced in a
// compromised process. See the security warning at the top of this file
//
// # Example
// ```
// ===== In one process =====
//
// auto maybeSharedMemory = SynchronizedSharedMemory<int>::CreateNew();
// maybeSharedMemory.RunWithLock([] (int* pInt) {
// *pInt = 5;
// });
//
// // ===== In another process =====
//
// int curValue = 0;
// maybeSharedMemory.RunWithLock([&] (int* pInt) {
// curValue = *pInt;
// });
//
// MOZ_ASSERT(curValue == 5);
// ```
template <typename Fn, typename... Args>
void RunWithLock(Fn&& aFn, Args&&... args) {
mDetail.LockMutex();
// Justification for volatile: We need to make a local copy of structure T
// so a compromised remote process can't exploit us by ignoring the lock
// and writing after we've performed validation and trust the data, but
// before we actually use it to do something privileged
//
// Without volatile, the optimizer may be allowed to evade the copy
// and continue to act on the original remote object (among other dangerous
// things)
//
T localCopy;
volatile_memcpy(&localCopy, mDetail.GetPtr(), sizeof(T));
(std::forward<Fn>(aFn))(&localCopy, std::forward<Args>(args)...);
volatile_memcpy(mDetail.GetPtr(), &localCopy, sizeof(T));
mDetail.UnlockMutex();
}
// Create from info generated using GenerateRemoteInfo()
//
// This is the other end to GenerateRemoteInfo(). It receives the
// serialized `SynchronizedSharedMemoryRemoteInfo` structure sent over IPDL
// and uses it to attach to the shared object.
//
// Returns `Nothing` if an error occurs.
//
// # Example
// ```
// // ===== One side of IPDL =====
//
// bool SetupSharedMemory(mozilla::ipc::PMyProtocol& aProtocol) {
// auto maybeRemoteInfo = mSharedMemory.GenerateRemoteInfo(&aProtocol);
// if (!maybeRemoteInfo) {
// return false;
// }
// return aProtocol.SendSharedMemoryInfo(*maybeRemoteInfo);
// }
//
// // ===== Other side of IPDL =====
//
// mozilla::ipc::IPCResult MyProtocol::RecvSharedMemoryInfo(
// const SynchronizedSharedMemoryRemoteInfo& aRemoteInfo) {
// auto maybeSharedMemory = SynchronizedSharedMemory::CreateFromRemoteInfo(
// aRemoteInfo);
// if (!maybeSharedMemory) {
// return IPC_FAIL_NO_REASON(this);
// }
// mSharedMemory = std::move(*maybeSharedMemory);
// return IPC_OK();
// }
// ```
static Maybe<SynchronizedSharedMemory> CreateFromRemoteInfo(
const SynchronizedSharedMemoryRemoteInfo& aIPCInfo) {
auto maybeDetail =
SynchronizedSharedMemoryDetail::CreateFromRemoteInfo(aIPCInfo);
if (!maybeDetail) {
return Nothing{};
}
return Some(SynchronizedSharedMemory(std::move(*maybeDetail)));
}
// Generate remote info needed to share object with specified actor
//
// Note that the returned information is likely specific to the actor. This
// should be called individually for each actor that will use the shared
// object.
//
// See `CreateFromRemoteInfo` for usage example
bool GenerateRemoteInfo(const mozilla::ipc::IProtocol* aActor,
SynchronizedSharedMemoryRemoteInfo* aOut) {
return mDetail.GenerateRemoteInfo(aActor, aOut);
}
// Allow move
SynchronizedSharedMemory(SynchronizedSharedMemory&&) = default;
SynchronizedSharedMemory& operator=(SynchronizedSharedMemory&&) = default;
// Disallow copy
SynchronizedSharedMemory(const SynchronizedSharedMemory&) = delete;
SynchronizedSharedMemory& operator=(const SynchronizedSharedMemory&) = delete;
~SynchronizedSharedMemory() = default;
private:
SynchronizedSharedMemory() = default;
explicit SynchronizedSharedMemory(SynchronizedSharedMemoryDetail aDetail)
: mDetail(std::move(aDetail)) {}
void volatile_memcpy(void* aDst, const void* aSrc, size_t aSize) {
const volatile unsigned char* src =
static_cast<const volatile unsigned char*>(aSrc);
volatile unsigned char* dst = static_cast<volatile unsigned char*>(aDst);
size_t remaining = aSize;
while (remaining) {
*dst = *src;
++src;
++dst;
--remaining;
}
}
SynchronizedSharedMemoryDetail mDetail;
// The following is for testing of this API. These should be accessed
// from `SynchronizedSharedMemoryTestFriend`. This is used because generating
// a mock IProtocol actor is a fair amount of work.
friend class SynchronizedSharedMemoryTestFriend;
bool GenerateTestRemoteInfo(SynchronizedSharedMemoryRemoteInfo* aOut) {
return mDetail.GenerateTestRemoteInfo(aOut);
}
};
} // namespace mozilla::dom
#endif // DOM_GAMEPAD_SYNCHRONIZEDSHAREDMEMORY_H_

View File

@ -1,12 +0,0 @@
/* 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/. */
namespace mozilla {
namespace dom {
struct GamepadStateBroadcastReceiverInfo {
};
} // namespace dom
} // namespace mozilla

View File

@ -1,87 +0,0 @@
/* -*- 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/dom/GamepadStateBroadcaster.h"
namespace mozilla::dom {
class GamepadStateBroadcaster::Impl {
public:
Impl() = default;
~Impl() = default;
};
// static
Maybe<GamepadStateBroadcaster> GamepadStateBroadcaster::Create() {
return Nothing{};
}
GamepadStateBroadcaster::~GamepadStateBroadcaster() = default;
bool GamepadStateBroadcaster::AddReceiverAndGenerateRemoteInfo(
const mozilla::ipc::IProtocol* aActor,
GamepadStateBroadcastReceiverInfo* aOut) {
MOZ_CRASH("Should never be called");
}
void GamepadStateBroadcaster::RemoveReceiver(
const mozilla::ipc::IProtocol* aActor) {
MOZ_CRASH("Should never be called");
}
void GamepadStateBroadcaster::AddGamepad(
GamepadHandle aHandle, const char* aID, GamepadMappingType aMapping,
GamepadHand aHand, uint32_t aNumButtons, uint32_t aNumAxes,
uint32_t aNumHaptics, uint32_t aNumLights, uint32_t aNumTouches) {
MOZ_CRASH("Should never be called");
}
void GamepadStateBroadcaster::RemoveGamepad(GamepadHandle aHandle) {
MOZ_CRASH("Should never be called");
}
void GamepadStateBroadcaster::NewAxisMoveEvent(GamepadHandle aHandle,
uint32_t aAxis, double aValue) {
MOZ_CRASH("Should never be called");
}
void GamepadStateBroadcaster::NewButtonEvent(GamepadHandle aHandle,
uint32_t aButton, bool aPressed,
bool aTouched, double aValue) {
MOZ_CRASH("Should never be called");
}
void GamepadStateBroadcaster::NewLightIndicatorTypeEvent(
GamepadHandle aHandle, uint32_t aLight, GamepadLightIndicatorType aType) {
MOZ_CRASH("Should never be called");
}
void GamepadStateBroadcaster::NewPoseEvent(GamepadHandle aHandle,
const GamepadPoseState& aState) {
MOZ_CRASH("Should never be called");
}
void GamepadStateBroadcaster::NewMultiTouchEvent(
GamepadHandle aHandle, uint32_t aTouchArrayIndex,
const GamepadTouchState& aState) {
MOZ_CRASH("Should never be called");
}
void GamepadStateBroadcaster::SendTestCommand(uint32_t aCommandId) {
MOZ_CRASH("Should never be called");
}
GamepadStateBroadcaster::GamepadStateBroadcaster(
GamepadStateBroadcaster&& aOther) = default;
GamepadStateBroadcaster& GamepadStateBroadcaster::operator=(
GamepadStateBroadcaster&& aOther) = default;
GamepadStateBroadcaster::GamepadStateBroadcaster() = default;
GamepadStateBroadcaster::GamepadStateBroadcaster(UniquePtr<Impl> aImpl)
: mImpl(std::move(aImpl)) {}
} // namespace mozilla::dom

View File

@ -1,49 +0,0 @@
/* -*- 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/dom/GamepadStateReceiver.h"
namespace mozilla::dom {
class GamepadStateReceiver::Impl {
public:
Impl() = default;
~Impl() = default;
};
// static
Maybe<GamepadStateReceiver> GamepadStateReceiver::Create(
const GamepadStateBroadcastReceiverInfo& aReceiverInfo) {
return Nothing{};
}
GamepadStateReceiver::~GamepadStateReceiver() = default;
GamepadStateReceiver::GamepadStateReceiver(GamepadStateReceiver&& aOther) =
default;
GamepadStateReceiver& GamepadStateReceiver::operator=(
GamepadStateReceiver&& aOther) = default;
GamepadStateReceiver::GamepadStateReceiver() = default;
GamepadStateReceiver::GamepadStateReceiver(UniquePtr<Impl> aImpl)
: mImpl(std::move(aImpl)) {}
bool GamepadStateReceiver::StartMonitoringThread(
const std::function<void(const GamepadChangeEvent&)>& aFn) {
MOZ_CRASH("Should never be called");
}
bool GamepadStateReceiver::StartMonitoringThreadForTesting(
const std::function<void(const GamepadChangeEvent&)>& aMonitorFn,
const std::function<void(uint32_t)>& aTestCommandFn) {
MOZ_CRASH("Should never be called");
}
void GamepadStateReceiver::StopMonitoringThread() {
MOZ_CRASH("Should never be called");
}
} // namespace mozilla::dom

View File

@ -1,64 +0,0 @@
/* -*- 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/. */
// This is the implementation of SynchronizedSharedMemory for unimplemented
// platforms
#include "mozilla/dom/SynchronizedSharedMemory.h"
#include "mozilla/dom/SynchronizedSharedMemoryRemoteInfo.h"
namespace mozilla::dom {
class SynchronizedSharedMemoryDetail::Impl {};
// static
Maybe<SynchronizedSharedMemoryDetail> SynchronizedSharedMemoryDetail::CreateNew(
uint32_t) {
return Nothing{};
}
void SynchronizedSharedMemoryDetail::LockMutex() {
MOZ_CRASH("Should never be called");
}
void SynchronizedSharedMemoryDetail::UnlockMutex() {
MOZ_CRASH("Should never be called");
}
void* SynchronizedSharedMemoryDetail::GetPtr() const {
MOZ_CRASH("Should never be called");
}
// static
Maybe<SynchronizedSharedMemoryDetail>
SynchronizedSharedMemoryDetail::CreateFromRemoteInfo(
const SynchronizedSharedMemoryRemoteInfo&) {
return Nothing{};
}
bool SynchronizedSharedMemoryDetail::GenerateRemoteInfo(
const mozilla::ipc::IProtocol*, SynchronizedSharedMemoryRemoteInfo*) {
MOZ_CRASH("Should never be called");
}
bool SynchronizedSharedMemoryDetail::GenerateTestRemoteInfo(
SynchronizedSharedMemoryRemoteInfo*) {
MOZ_CRASH("Should never be called");
}
SynchronizedSharedMemoryDetail::SynchronizedSharedMemoryDetail() = default;
SynchronizedSharedMemoryDetail::~SynchronizedSharedMemoryDetail() = default;
SynchronizedSharedMemoryDetail::SynchronizedSharedMemoryDetail(
UniquePtr<Impl> aImpl)
: mImpl(std::move(aImpl)) {}
SynchronizedSharedMemoryDetail::SynchronizedSharedMemoryDetail(
SynchronizedSharedMemoryDetail&&) = default;
SynchronizedSharedMemoryDetail& SynchronizedSharedMemoryDetail::operator=(
SynchronizedSharedMemoryDetail&&) = default;
} // namespace mozilla::dom

View File

@ -1,12 +0,0 @@
/* 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/. */
namespace mozilla {
namespace dom {
struct SynchronizedSharedMemoryRemoteInfo {
};
} // namespace dom
} // namespace mozilla

View File

@ -34,18 +34,6 @@ already_AddRefed<GamepadEventChannelChild> GamepadEventChannelChild::Create() {
.forget();
}
mozilla::ipc::IPCResult GamepadEventChannelChild::RecvSetupSharedMemory(
const GamepadStateBroadcastReceiverInfo& aReceiverInfo) {
NS_DispatchToMainThread(NS_NewRunnableFunction(
"GamepadEventChannelChild::RecvSetupSharedMemory", [aReceiverInfo] {
RefPtr<GamepadManager> svc(GamepadManager::GetService());
if (svc) {
svc->SetupRemoteInfo(aReceiverInfo);
}
}));
return IPC_OK();
}
mozilla::ipc::IPCResult GamepadEventChannelChild::RecvGamepadUpdate(
const GamepadChangeEvent& aGamepadEvent) {
DebugOnly<nsresult> rv =

View File

@ -18,8 +18,6 @@ class GamepadEventChannelChild final : public PGamepadEventChannelChild {
static already_AddRefed<GamepadEventChannelChild> Create();
mozilla::ipc::IPCResult RecvSetupSharedMemory(
const GamepadStateBroadcastReceiverInfo& aReceiverInfo);
mozilla::ipc::IPCResult RecvGamepadUpdate(
const GamepadChangeEvent& aGamepadEvent);
mozilla::ipc::IPCResult RecvReplyGamepadPromise(const uint32_t& aPromiseID);

View File

@ -44,25 +44,23 @@ GamepadEventChannelParent::Create() {
.forget();
}
GamepadEventChannelParent::GamepadEventChannelParent() {
GamepadEventChannelParent::GamepadEventChannelParent() : mIsShutdown{false} {
MOZ_DIAGNOSTIC_ASSERT(IsOnBackgroundThread());
mBackgroundEventTarget = GetCurrentEventTarget();
}
bool GamepadEventChannelParent::ActorInit() {
AssertIsOnBackgroundThread();
RefPtr<GamepadPlatformService> service =
GamepadPlatformService::GetParentService();
MOZ_ASSERT(service);
service->AddChannelParent(this);
return true;
service->AddChannelParent(this);
}
void GamepadEventChannelParent::ActorDestroy(ActorDestroyReason aWhy) {
AssertIsOnBackgroundThread();
MOZ_DIAGNOSTIC_ASSERT(IsOnBackgroundThread());
MOZ_DIAGNOSTIC_ASSERT(!mIsShutdown);
mIsShutdown = true;
RefPtr<GamepadPlatformService> service =
GamepadPlatformService::GetParentService();

View File

@ -16,8 +16,6 @@ class GamepadEventChannelParent final : public PGamepadEventChannelParent {
NS_INLINE_DECL_THREADSAFE_REFCOUNTING(GamepadEventChannelParent, override)
static already_AddRefed<GamepadEventChannelParent> Create();
bool ActorInit();
void ActorDestroy(ActorDestroyReason aWhy) override;
mozilla::ipc::IPCResult RecvVibrateHaptic(
@ -43,6 +41,7 @@ class GamepadEventChannelParent final : public PGamepadEventChannelParent {
GamepadEventChannelParent();
~GamepadEventChannelParent() = default;
bool mIsShutdown;
nsCOMPtr<nsIEventTarget> mBackgroundEventTarget;
};

View File

@ -4,7 +4,6 @@
include protocol PBackground;
include "mozilla/dom/GamepadMessageUtils.h";
include GamepadEventTypes;
include GamepadStateBroadcastReceiverInfo;
using mozilla::dom::GamepadHandle from "mozilla/dom/GamepadHandle.h";
@ -23,7 +22,6 @@ refcounted protocol PGamepadEventChannel {
uint8_t aRed, uint8_t aGreen, uint8_t aBlue, uint32_t aPromiseID);
child:
async SetupSharedMemory(GamepadStateBroadcastReceiverInfo aReceiverInfo);
async GamepadUpdate(GamepadChangeEvent aGamepadEvent);
async ReplyGamepadPromise(uint32_t aPromiseID);
};

View File

@ -26,8 +26,6 @@ EXPORTS.mozilla.dom += [
"GamepadPoseState.h",
"GamepadRemapping.h",
"GamepadServiceTest.h",
"GamepadStateBroadcaster.h",
"GamepadStateReceiver.h",
"GamepadTouch.h",
"GamepadTouchState.h",
"ipc/GamepadEventChannelChild.h",
@ -35,7 +33,6 @@ EXPORTS.mozilla.dom += [
"ipc/GamepadMessageUtils.h",
"ipc/GamepadTestChannelChild.h",
"ipc/GamepadTestChannelParent.h",
"SynchronizedSharedMemory.h",
]
UNIFIED_SOURCES = [
@ -67,33 +64,10 @@ elif CONFIG["OS_ARCH"] == "Linux":
else:
UNIFIED_SOURCES += ["fallback/FallbackGamepad.cpp"]
if CONFIG["MOZ_WIDGET_TOOLKIT"] == "windows":
IPDL_SOURCES += [
"windows/GamepadStateBroadcastReceiverInfo.ipdlh",
"windows/SynchronizedSharedMemoryRemoteInfo.ipdlh",
]
SOURCES += [
"windows/GamepadStateBroadcaster.cpp",
"windows/GamepadStateReceiver.cpp",
"windows/SynchronizedSharedMemory.cpp",
]
else:
IPDL_SOURCES += [
"fallback/GamepadStateBroadcastReceiverInfo.ipdlh",
"fallback/SynchronizedSharedMemoryRemoteInfo.ipdlh",
]
SOURCES += [
"fallback/GamepadStateBroadcaster.cpp",
"fallback/GamepadStateReceiver.cpp",
"fallback/SynchronizedSharedMemory.cpp",
]
LOCAL_INCLUDES += [
"ipc",
]
TEST_DIRS += ["tests/gtest"]
include("/ipc/chromium/chromium-config.mozbuild")
FINAL_LIBRARY = "xul"

View File

@ -1,451 +0,0 @@
/* 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/dom/GamepadStateBroadcaster.h"
#include "mozilla/dom/GamepadStateReceiver.h"
#include "mozilla/dom/GamepadStateBroadcastReceiverInfo.h"
#include "mozilla/dom/GamepadEventTypes.h"
#include <array>
#include <condition_variable>
#include <list>
#include <map>
#include <mutex>
#include <random>
#include <string>
#include <thread>
#include <variant>
#include <vector>
#include "gtest/gtest.h"
#define MYASSERT(x) \
do { \
if (!(x)) { \
ADD_FAILURE() << "MYASSERT FAILED: `" #x "`\n"; \
abort(); \
} \
} while (false)
namespace mozilla::dom {
// This class has friend access to GamepadHandle and can assist in creating
// them
class GamepadTestHelper {
public:
static void SendTestCommand(GamepadStateBroadcaster* p, uint32_t commandId) {
p->SendTestCommand(commandId);
}
static bool StartMonitoringThreadForTesting(
GamepadStateReceiver* p,
std::function<void(const GamepadChangeEvent&)> aMonitorFn,
std::function<void(uint32_t)> aTestCommandFn) {
return p->StartMonitoringThreadForTesting(aMonitorFn, aTestCommandFn);
}
static GamepadHandle GetTestHandle(uint32_t value) {
return GamepadHandle(value, GamepadHandleKind::GamepadPlatformManager);
}
};
namespace {
template <typename T>
class EventQueue {
public:
void Enqueue(T t) {
std::unique_lock<std::mutex> lock(mMutex);
mList.emplace_back(std::move(t));
mCond.notify_one();
}
T Dequeue() {
std::unique_lock<std::mutex> lock(mMutex);
while (mList.empty()) {
mCond.wait(lock);
}
T result = std::move(mList.front());
mList.pop_front();
return result;
}
EventQueue() = default;
~EventQueue() = default;
// Disallow copy and move
EventQueue(const EventQueue&) = delete;
EventQueue& operator=(const EventQueue&) = delete;
EventQueue(EventQueue&&) = delete;
EventQueue& operator=(EventQueue&&) = delete;
private:
std::mutex mMutex{};
std::condition_variable mCond{};
std::list<T> mList{};
};
class Signal {
public:
void SetSignal() {
std::unique_lock<std::mutex> lock(mMutex);
mSignalled = true;
mCond.notify_one();
}
void WaitForSignal() {
std::unique_lock<std::mutex> lock(mMutex);
while (!mSignalled) {
mCond.wait(lock);
}
mSignalled = false;
}
Signal() = default;
~Signal() = default;
// Disallow copy and move
Signal(const Signal&) = delete;
Signal& operator=(const Signal&) = delete;
Signal(Signal&&) = delete;
Signal& operator=(Signal&&) = delete;
private:
std::mutex mMutex{};
std::condition_variable mCond{};
bool mSignalled{};
};
constexpr uint32_t kMaxButtons = 8;
constexpr uint32_t kMaxAxes = 4;
constexpr uint32_t kCommandSelfCheck = 1;
constexpr uint32_t kCommandQuit = 2;
struct GamepadButton {
double value{};
bool pressed{};
bool touched{};
};
struct GamepadInfo {
nsString id{};
GamepadMappingType mapping{};
GamepadHand hand{};
uint32_t numButtons{};
uint32_t numAxes{};
std::array<GamepadButton, kMaxButtons> buttons{};
std::array<double, kMaxAxes> axes{};
};
static void ExpectEqual(const GamepadButton& a, const GamepadButton& b) {
EXPECT_EQ(a.value, b.value);
EXPECT_EQ(a.pressed, b.pressed);
EXPECT_EQ(a.touched, b.touched);
}
static void ExpectEqual(const GamepadInfo& a, const GamepadInfo& b) {
EXPECT_EQ(a.id, b.id);
EXPECT_EQ(a.mapping, b.mapping);
EXPECT_EQ(a.hand, b.hand);
EXPECT_EQ(a.numButtons, b.numButtons);
EXPECT_EQ(a.numAxes, b.numAxes);
for (size_t i = 0; i < a.numButtons; ++i) {
ExpectEqual(a.buttons[i], b.buttons[i]);
}
for (size_t i = 0; i < a.numAxes; ++i) {
EXPECT_EQ(a.axes[i], b.axes[i]);
}
}
static void ExpectEqual(const std::map<GamepadHandle, GamepadInfo>& a,
const std::map<GamepadHandle, GamepadInfo>& b) {
for (auto& pair : a) {
EXPECT_TRUE(b.count(pair.first) == 1);
ExpectEqual(a.at(pair.first), b.at(pair.first));
}
}
class TestThread {
public:
TestThread() = default;
~TestThread() = default;
void Run(GamepadStateBroadcastReceiverInfo remoteInfo) {
mThread = std::thread(&TestThread::ThreadFunc, this, remoteInfo);
}
void Join() { mThread.join(); }
void SetExpectedState(std::map<GamepadHandle, GamepadInfo> expectedState) {
std::unique_lock<std::mutex> lock(mExpectedStateMutex);
mExpectedState = std::move(expectedState);
}
void WaitCommandProcessed() { mCommandProcessed.WaitForSignal(); }
// Disallow move and copy
TestThread(const TestThread&) = delete;
TestThread& operator=(const TestThread&) = delete;
TestThread(TestThread&&) = delete;
TestThread& operator=(TestThread&&) = delete;
private:
void HandleGamepadEvent(std::map<GamepadHandle, GamepadInfo>& gamepads,
const GamepadChangeEvent& event) {
GamepadHandle handle = event.handle();
switch (event.body().type()) {
case GamepadChangeEventBody::TGamepadAdded: {
MYASSERT(gamepads.count(handle) == 0);
const GamepadAdded& x = event.body().get_GamepadAdded();
GamepadInfo info{};
info.id = x.id();
info.mapping = x.mapping();
info.hand = x.hand();
info.numButtons = x.num_buttons();
info.numAxes = x.num_axes();
gamepads.insert(std::make_pair(handle, info));
break;
}
case GamepadChangeEventBody::TGamepadRemoved: {
MYASSERT(gamepads.count(handle) == 1);
gamepads.erase(handle);
break;
}
case GamepadChangeEventBody::TGamepadAxisInformation: {
const GamepadAxisInformation& x =
event.body().get_GamepadAxisInformation();
gamepads[handle].axes[x.axis()] = x.value();
break;
}
case GamepadChangeEventBody::TGamepadButtonInformation: {
const GamepadButtonInformation& x =
event.body().get_GamepadButtonInformation();
gamepads[handle].buttons[x.button()].value = x.value();
gamepads[handle].buttons[x.button()].pressed = x.pressed();
gamepads[handle].buttons[x.button()].touched = x.touched();
break;
}
case GamepadChangeEventBody::T__None:
case GamepadChangeEventBody::TGamepadHandInformation:
case GamepadChangeEventBody::TGamepadLightIndicatorTypeInformation:
case GamepadChangeEventBody::TGamepadPoseInformation:
case GamepadChangeEventBody::TGamepadTouchInformation: {
MYASSERT(false);
break;
}
}
}
bool ProcessCommand(std::map<GamepadHandle, GamepadInfo>& gamepads,
uint32_t commandId) {
if (commandId == kCommandQuit) {
return true;
}
MYASSERT(commandId == kCommandSelfCheck);
std::unique_lock<std::mutex> lock(mExpectedStateMutex);
ExpectEqual(mExpectedState, gamepads);
return false;
}
void ThreadFunc(GamepadStateBroadcastReceiverInfo remoteInfo) {
std::random_device rd;
std::mt19937 mersenne_twister(rd());
std::uniform_int_distribution<uint32_t> randomDelayGenerator(1, 1000);
Maybe<GamepadStateReceiver> maybeReceiver =
GamepadStateReceiver::Create(remoteInfo);
MYASSERT(maybeReceiver);
EventQueue<std::variant<GamepadChangeEvent, uint32_t>> eventQueue{};
bool result = GamepadTestHelper::StartMonitoringThreadForTesting(
&*maybeReceiver,
[&](const GamepadChangeEvent& e) {
// Simulate that sometimes there will be thread noise and make sure
// that we still end up with the correct state anyway
uint32_t randomDelay = randomDelayGenerator(mersenne_twister);
std::this_thread::sleep_for(std::chrono::microseconds(randomDelay));
eventQueue.Enqueue(e);
},
[&](uint32_t x) { eventQueue.Enqueue(x); });
MYASSERT(result);
std::map<GamepadHandle, GamepadInfo> gamepads{};
bool quit = false;
while (!quit) {
auto event = eventQueue.Dequeue();
if (std::holds_alternative<GamepadChangeEvent>(event)) {
HandleGamepadEvent(gamepads, std::get<GamepadChangeEvent>(event));
} else {
quit = ProcessCommand(gamepads, std::get<uint32_t>(event));
mCommandProcessed.SetSignal();
}
}
maybeReceiver->StopMonitoringThread();
}
std::thread mThread{};
std::mutex mExpectedStateMutex{};
std::map<GamepadHandle, GamepadInfo> mExpectedState{};
Signal mCommandProcessed{};
};
} // anonymous namespace
TEST(GamepadStateBroadcastTest, Multithreaded)
{
constexpr uint8_t kNumThreads = 4;
const GamepadHandle testHandle0 = GamepadTestHelper::GetTestHandle(1234);
const GamepadHandle testHandle1 = GamepadTestHelper::GetTestHandle(2345);
const GamepadHandle testHandle2 = GamepadTestHelper::GetTestHandle(3456);
std::map<GamepadHandle, GamepadInfo> expected{};
Maybe<GamepadStateBroadcaster> maybeBroadcaster =
GamepadStateBroadcaster::Create();
MYASSERT(maybeBroadcaster);
std::array<TestThread, kNumThreads> remoteThreads{};
for (uint8_t i = 0; i < kNumThreads; ++i) {
GamepadStateBroadcastReceiverInfo remoteInfo{};
bool result = maybeBroadcaster->AddReceiverAndGenerateRemoteInfo(
nullptr, &remoteInfo);
MYASSERT(result);
remoteThreads[i].Run(remoteInfo);
}
// This function can be used at any time to tell the threads to verify
// that their state matches `expected` after they've finished processing
// all previous updates
auto checkExpectedState = [&] {
for (uint8_t i = 0; i < kNumThreads; ++i) {
remoteThreads[i].SetExpectedState(expected);
}
GamepadTestHelper::SendTestCommand(&*maybeBroadcaster, kCommandSelfCheck);
for (uint8_t i = 0; i < kNumThreads; ++i) {
remoteThreads[i].WaitCommandProcessed();
}
};
// Simple one - Just add a gamepad and verify it shows up
maybeBroadcaster->AddGamepad(testHandle0, "TestId0",
GamepadMappingType::Standard, GamepadHand::Left,
kMaxButtons, kMaxAxes, 0, 0, 0);
GamepadInfo* info0 = &expected[testHandle0];
info0->id = nsString(u"TestId0");
info0->mapping = GamepadMappingType::Standard;
info0->hand = GamepadHand::Left;
info0->numButtons = kMaxButtons;
info0->numAxes = kMaxAxes;
checkExpectedState();
// Try adding 2 more and see that they also show up
maybeBroadcaster->AddGamepad(testHandle1, "TestId1",
GamepadMappingType::Standard, GamepadHand::Left,
2, 2, 0, 0, 0);
maybeBroadcaster->AddGamepad(testHandle2, "TestId2",
GamepadMappingType::Standard, GamepadHand::Left,
3, 2, 0, 0, 0);
GamepadInfo* info1 = &expected[testHandle1];
info1->id = nsString(u"TestId1");
info1->mapping = GamepadMappingType::Standard;
info1->hand = GamepadHand::Left;
info1->numButtons = 2;
info1->numAxes = 2;
GamepadInfo* info2 = &expected[testHandle2];
info2->id = nsString(u"TestId2");
info2->mapping = GamepadMappingType::Standard;
info2->hand = GamepadHand::Left;
info2->numButtons = 3;
info2->numAxes = 2;
checkExpectedState();
// Press some buttons, move some axes, unplug one of the gamepads
maybeBroadcaster->NewButtonEvent(testHandle1, 0, true, true, 0.5);
maybeBroadcaster->NewButtonEvent(testHandle2, 0, true, false, 0.25);
maybeBroadcaster->NewButtonEvent(testHandle2, 1, true, true, 0.35);
maybeBroadcaster->NewAxisMoveEvent(testHandle1, 1, 0.5);
maybeBroadcaster->NewAxisMoveEvent(testHandle1, 1, 0.75);
maybeBroadcaster->NewButtonEvent(testHandle0, 0, true, true, 0.1);
maybeBroadcaster->NewAxisMoveEvent(testHandle1, 1, 0.8);
maybeBroadcaster->NewAxisMoveEvent(testHandle2, 0, 1.0);
maybeBroadcaster->NewButtonEvent(testHandle2, 0, false, false, 0.0);
maybeBroadcaster->RemoveGamepad(testHandle0);
info0 = nullptr;
expected.erase(testHandle0);
info1->buttons[0].pressed = true;
info1->buttons[0].touched = true;
info1->buttons[0].value = 0.5;
info1->axes[1] = 0.8;
info2->buttons[0].pressed = false;
info2->buttons[0].touched = false;
info2->buttons[0].value = 0.0;
info2->buttons[1].pressed = true;
info2->buttons[1].touched = true;
info2->buttons[1].value = 0.35;
info2->axes[0] = 1.0;
checkExpectedState();
// Re-add gamepad0 and ensure it starts off in a null state
maybeBroadcaster->AddGamepad(testHandle0, "TestId0",
GamepadMappingType::Standard, GamepadHand::Left,
kMaxButtons, kMaxAxes, 0, 0, 0);
info0 = &expected[testHandle0];
info0->id = nsString(u"TestId0");
info0->mapping = GamepadMappingType::Standard;
info0->hand = GamepadHand::Left;
info0->numButtons = kMaxButtons;
info0->numAxes = kMaxAxes;
checkExpectedState();
// Tell the receiver to shut everything down and exit its thread
GamepadTestHelper::SendTestCommand(&*maybeBroadcaster, kCommandQuit);
for (uint8_t i = 0; i < kNumThreads; ++i) {
remoteThreads[i].WaitCommandProcessed();
}
for (uint8_t i = 0; i < kNumThreads; ++i) {
remoteThreads[i].Join();
}
}
} // namespace mozilla::dom

View File

@ -1,19 +0,0 @@
#include "mozilla/dom/SynchronizedSharedMemory.h"
#include "mozilla/dom/SynchronizedSharedMemoryRemoteInfo.h"
#include "gtest/gtest.h"
// Dead-simple test for the shared memory fallback: As long as CreateNew()
// returns `Nothing`, there really isn't anything else that can be done with
// the API
namespace mozilla::dom {
// Ensure that we can't create SynchronizedSharedMemory
TEST(SynchronizedSharedMemoryTest, NoCreate)
{
auto maybeSharedMemory = SynchronizedSharedMemory<int>::CreateNew();
ASSERT_TRUE(!maybeSharedMemory);
}
} // namespace mozilla::dom

View File

@ -1,183 +0,0 @@
#include "mozilla/dom/SynchronizedSharedMemory.h"
#include "mozilla/dom/SynchronizedSharedMemoryRemoteInfo.h"
#include <array>
#include <atomic>
#include <chrono>
#include <thread>
#include <windows.h>
#include "gtest/gtest.h"
namespace mozilla::dom {
// Helpers that have private access to SynchronizedSharedMemory
// (As it turns out, creating an entire IPDL actor tree just for a test is
// hard)
class SynchronizedSharedMemoryTestFriend {
public:
template <typename T>
static bool GenerateRemoteInfo(SynchronizedSharedMemory<T>& aSharedMemory,
SynchronizedSharedMemoryRemoteInfo* aOut) {
return aSharedMemory.GenerateTestRemoteInfo(aOut);
}
};
// A basic test that ensures that reading/writing even works
TEST(SynchronizedSharedMemoryTest, Basic)
{
auto maybeLocalObject = SynchronizedSharedMemory<int>::CreateNew(0);
ASSERT_TRUE(maybeLocalObject);
SynchronizedSharedMemoryRemoteInfo remoteInfo;
ASSERT_TRUE(SynchronizedSharedMemoryTestFriend::GenerateRemoteInfo(
*maybeLocalObject, &remoteInfo));
auto maybeRemoteObject =
SynchronizedSharedMemory<int>::CreateFromRemoteInfo(remoteInfo);
ASSERT_TRUE(maybeRemoteObject);
(*maybeLocalObject).RunWithLock([](int* ptr, int value) { *ptr = value; }, 5);
int value = 0;
(*maybeRemoteObject).RunWithLock([&value](int* ptr) { value = *ptr; });
ASSERT_EQ(value, 5);
}
// Multi-threaded test (to try and simulate multi-process use case)
//
// Create a shared object, create a bunch of threads each with their own
// remote handle to the object, and have each thread perform an action
// that would be unsafe if locking were not working properly.
//
// In this case, the threads are incrementing the elements of an array by a
// a constant value, and the array is intentionally large enough that it can't
// fit in a cache line.
//
// Each thread will assert that it never sees a version of the array where all
// the elements aren't equal, and the main thread won't stop the test until it's
// seen a minimum amount of changes made to the entire array without any
// inconsistency.
TEST(SynchronizedSharedMemoryTest, Multithreaded)
{
struct TestStruct {
// Big enough that it can't fit into a single cache line (512 bits on x86)
std::array<uint64_t, 16> data;
void WriteValueToAll(uint64_t value) {
for (auto& x : data) {
x = value;
}
}
void AddValueToAll(uint64_t value) {
for (auto& x : data) {
x += value;
}
}
void AssertAllEqual(uint64_t value) const {
for (size_t i = 0; i < (data.size() - 1); ++i) {
ASSERT_EQ(data[i], data[i + 1]);
}
}
void AssertEq(const TestStruct& other) const {
for (size_t i = 0; i < data.size(); ++i) {
ASSERT_EQ(data[i], other.data[i]);
}
}
};
using SharedType = SynchronizedSharedMemory<TestStruct>;
// Some useful settings for the test
// Number of worker threads to spin up
constexpr size_t kNumThreads = 4;
// Value to start each element at
constexpr uint64_t kTestStartValue = 0x1234;
// Value to increment each element by
constexpr uint64_t kTestIncrementAmount = 0x5678;
// Number of changes the main thread must see before passing
constexpr uint32_t kMinimumChangesToWitness = 10000;
// Initial value to test that the implementation correctly handles copy
// construction of the contained object
constexpr TestStruct kInitialStruct = {0, 1, 2, 3, 4, 5, 6, 7,
8, 9, 10, 11, 12, 13, 14, 15};
// Create the shared object, ensure it constructed properly, set all elements
// to kTestStartValue, and assert that it worked
auto maybeLocalObject = SharedType::CreateNew(kInitialStruct);
ASSERT_TRUE(maybeLocalObject);
maybeLocalObject->RunWithLock([&](TestStruct* shared) {
shared->AssertEq(kInitialStruct);
shared->WriteValueToAll(kTestStartValue);
shared->AssertAllEqual(kTestStartValue);
});
// This variable will be set on the main thread when it has seen enough
// changes happen, causing the threads to end
std::atomic_bool done{false};
// Fire up the worker threads, each with their own "remote info", and have
// them add a constant to each element in the array while holding the lock
// If any thread ever witnesses a situation where all elements aren't equal,
// the test is failed
std::array<std::thread, kNumThreads> workerThreads;
for (auto& t : workerThreads) {
SynchronizedSharedMemoryRemoteInfo remoteInfo;
ASSERT_TRUE(SynchronizedSharedMemoryTestFriend::GenerateRemoteInfo(
*maybeLocalObject, &remoteInfo));
t = std::thread([remoteInfo, &done] {
auto maybeRemoteObject = SharedType::CreateFromRemoteInfo(remoteInfo);
ASSERT_TRUE(maybeRemoteObject);
while (!done.load(std::memory_order_relaxed)) {
maybeRemoteObject->RunWithLock([&](TestStruct* shared) {
shared->AssertAllEqual(shared->data[0]);
shared->AddValueToAll(kTestIncrementAmount);
shared->AssertAllEqual(shared->data[0]);
});
}
});
}
// The main thread will just keep locking the mutex and sampling the state
// of the array, ensuring all elements are equal and keeping track of the
// number of times it's seen the value change.
// Once it's seen `kMinimumChangesToWitness` changes occur, it will set
// `done` to signal all the threads to stop and join with them.
uint64_t numChangesWitnessed = 0;
uint64_t lastValueWitnessed = kTestStartValue;
while (numChangesWitnessed < kMinimumChangesToWitness) {
// Grab the current value of the array and ensure they're all equal
uint64_t valueWitnessed = 0;
maybeLocalObject->RunWithLock([&](TestStruct* shared) {
valueWitnessed = shared->data[0];
shared->AssertAllEqual(valueWitnessed);
});
// If the value has changed, take note
if (lastValueWitnessed != valueWitnessed) {
++numChangesWitnessed;
lastValueWitnessed = valueWitnessed;
}
}
// We passed! Shut it all down
done.store(true, std::memory_order_relaxed);
for (auto& t : workerThreads) {
t.join();
}
}
} // namespace mozilla::dom

View File

@ -1,17 +0,0 @@
# 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/.
if CONFIG["MOZ_WIDGET_TOOLKIT"] == "windows":
SOURCES = [
"TestGamepadStateBroadcastWindows.cpp",
"TestSynchronizedSharedMemoryWindows.cpp",
]
else:
SOURCES = [
"TestSynchronizedSharedMemoryFallback.cpp",
]
FINAL_LIBRARY = "xul-gtest"
include("/ipc/chromium/chromium-config.mozbuild")

View File

@ -1,17 +0,0 @@
/* 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 SynchronizedSharedMemoryRemoteInfo;
using mozilla::WindowsHandle from "mozilla/ipc/IPCTypes.h";
namespace mozilla {
namespace dom {
struct GamepadStateBroadcastReceiverInfo {
SynchronizedSharedMemoryRemoteInfo sharedMemoryInfo;
WindowsHandle eventHandle;
};
} // namespace dom
} // namespace mozilla

View File

@ -1,338 +0,0 @@
/* -*- 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/dom/GamepadStateBroadcaster.h"
#include "GamepadStateLayout.h"
#include "GamepadWindowsUtil.h"
#include "mozilla/dom/GamepadStateBroadcastReceiverInfo.h"
#include "mozilla/dom/GamepadEventTypes.h"
#include "mozilla/dom/SynchronizedSharedMemory.h"
#include "mozilla/ipc/ProtocolUtils.h"
#include "mozilla/HashTable.h"
#include <inttypes.h>
#include <windows.h>
namespace mozilla::dom {
using SharedState = SynchronizedSharedMemory<GamepadSystemState>;
class GamepadStateBroadcaster::Impl {
public:
static UniquePtr<Impl> Create() {
Maybe<SharedState> sharedState = SharedState::CreateNew();
if (!sharedState) {
return nullptr;
}
return UniquePtr<Impl>(new Impl(std::move(*sharedState)));
}
bool AddReceiverAndGenerateRemoteInfo(
const mozilla::ipc::IProtocol* aActor,
GamepadStateBroadcastReceiverInfo* aOut) {
MOZ_ASSERT(aOut);
DWORD targetPID = aActor ? aActor->OtherPid() : ::GetCurrentProcessId();
// Create the NT event and a remote handle for it
UniqueHandle<NTEventHandleTraits> eventHandle(
::CreateEvent(nullptr /*no ACL*/, FALSE /*not manual*/, FALSE /*unset*/,
nullptr /*no name*/));
if (!eventHandle) {
return false;
}
HANDLE remoteEventHandle = nullptr;
if (!mozilla::ipc::DuplicateHandle(eventHandle.Get(), targetPID,
&remoteEventHandle, 0,
DUPLICATE_SAME_ACCESS)) {
return false;
}
// Generate a remote handle for the shared memory
SynchronizedSharedMemoryRemoteInfo sharedMemoryRemoteInfo;
if (!mSharedState.GenerateRemoteInfo(aActor, &sharedMemoryRemoteInfo)) {
// There is no reasonable way to clean up remoteEventHandle here, since
// our process doesn't own it
return false;
}
MOZ_ALWAYS_TRUE(mBroadcastEventHandles.append(
BroadcastEventHandle{aActor, std::move(eventHandle)}));
// The event and the shared memory are everything the remote side needs
aOut->sharedMemoryInfo() = std::move(sharedMemoryRemoteInfo);
aOut->eventHandle() = reinterpret_cast<WindowsHandle>(remoteEventHandle);
return true;
}
void RemoveReceiver(const mozilla::ipc::IProtocol* aActor) {
auto* ptr = [&]() -> BroadcastEventHandle* {
for (auto& x : mBroadcastEventHandles) {
if (x.actor == aActor) {
return &x;
}
}
return nullptr;
}();
if (!ptr) {
MOZ_ASSERT(false, "Tried to remove a receiver that was never added");
return;
}
// We don't care about order, so we can remove an entry by overwriting
// it with the last element and then popping the last element
if (ptr != &mBroadcastEventHandles.back()) {
(*ptr) = std::move(mBroadcastEventHandles.back());
}
mBroadcastEventHandles.popBack();
}
void AddGamepad(GamepadHandle aHandle, const char* aID,
GamepadMappingType aMapping, GamepadHand aHand,
uint32_t aNumButtons, uint32_t aNumAxes, uint32_t aNumHaptics,
uint32_t aNumLights, uint32_t aNumTouches) {
size_t lenId = strlen(aID);
MOZ_RELEASE_ASSERT(lenId <= kMaxGamepadIdLength);
MOZ_RELEASE_ASSERT(aNumButtons <= kMaxButtonsPerGamepad);
MOZ_RELEASE_ASSERT(aNumAxes <= kMaxAxesPerGamepad);
MOZ_RELEASE_ASSERT(aNumLights <= kMaxLightsPerGamepad);
MOZ_RELEASE_ASSERT(aNumTouches <= kMaxNumMultiTouches);
// We pass an empty handle as the first argument, which tells
// ModifyGamepadSlot to run the lambda on the first empty slot that's
// found.
//
// If there are no empty slots, the following lambda won't be run and
// the add will fail silently (which is preferable to crashing)
ModifyGamepadSlot(GamepadHandle{}, [&](GamepadSlot& slot) {
slot.handle = aHandle;
memcpy(&slot.props.id[0], aID, lenId);
slot.props.id[lenId] = 0;
slot.props.mapping = aMapping;
slot.props.hand = aHand;
slot.props.numButtons = aNumButtons;
slot.props.numAxes = aNumAxes;
slot.props.numHaptics = aNumHaptics;
slot.props.numLights = aNumLights;
slot.props.numTouches = aNumTouches;
});
}
void RemoveGamepad(GamepadHandle aHandle) {
// If the handle was never added, this function will do nothing
ModifyGamepadSlot(aHandle,
[&](GamepadSlot& slot) { slot = GamepadSlot{}; });
}
void NewAxisMoveEvent(GamepadHandle aHandle, uint32_t aAxis, double aValue) {
MOZ_RELEASE_ASSERT(aAxis < kMaxAxesPerGamepad);
// If the handle was never added, this function will do nothing
ModifyGamepadSlot(aHandle, [&](GamepadSlot& slot) {
MOZ_ASSERT(aAxis < slot.props.numAxes);
slot.values.axes[aAxis] = aValue;
});
}
void NewButtonEvent(GamepadHandle aHandle, uint32_t aButton, bool aPressed,
bool aTouched, double aValue) {
MOZ_RELEASE_ASSERT(aButton < kMaxButtonsPerGamepad);
// If the handle was never added, this function will do nothing
ModifyGamepadSlot(aHandle, [&](GamepadSlot& slot) {
MOZ_ASSERT(aButton < slot.props.numButtons);
slot.values.buttonValues[aButton] = aValue;
slot.values.buttonPressedBits[aButton] = aPressed;
slot.values.buttonTouchedBits[aButton] = aTouched;
});
}
void NewLightIndicatorTypeEvent(GamepadHandle aHandle, uint32_t aLight,
GamepadLightIndicatorType aType) {
MOZ_RELEASE_ASSERT(aLight < kMaxLightsPerGamepad);
// If the handle was never added, this function will do nothing
ModifyGamepadSlot(aHandle, [&](GamepadSlot& slot) {
MOZ_ASSERT(aLight < slot.props.numLights);
slot.values.lights[aLight] = aType;
});
}
void NewPoseEvent(GamepadHandle aHandle, const GamepadPoseState& aState) {
// If the handle was never added, this function will do nothing
ModifyGamepadSlot(aHandle,
[&](GamepadSlot& slot) { slot.values.pose = aState; });
}
void NewMultiTouchEvent(GamepadHandle aHandle, uint32_t aTouchArrayIndex,
const GamepadTouchState& aState) {
MOZ_RELEASE_ASSERT(aTouchArrayIndex < kMaxNumMultiTouches);
// If the handle was never added, this function will do nothing
ModifyGamepadSlot(aHandle, [&](GamepadSlot& slot) {
MOZ_ASSERT(aTouchArrayIndex < slot.props.numTouches);
slot.values.touches[aTouchArrayIndex] = aState;
});
}
void SendTestCommand(uint32_t aCommandId) {
mSharedState.RunWithLock([&](GamepadSystemState* p) {
// SECURITY BOUNDARY -- `GamepadSystemState* p` is a local copy of the
// shared memory (it is not a live copy), and we only write to it here.
// It is also only used for testing
p->testCommandId = aCommandId;
p->testCommandTrigger = !p->testCommandTrigger;
p->changeId = mChangeId;
++mChangeId;
});
TriggerEvents();
}
// Disallow copy/move
Impl(const Impl&) = delete;
Impl& operator=(const Impl&) = delete;
Impl(Impl&&) = delete;
Impl& operator=(Impl&&) = delete;
private:
struct BroadcastEventHandle {
const mozilla::ipc::IProtocol* actor;
UniqueHandle<NTEventHandleTraits> eventHandle;
};
explicit Impl(SharedState aSharedState)
: mChangeId(1), mSharedState(std::move(aSharedState)) {}
void ModifyGamepadSlot(GamepadHandle aHandle,
const std::function<void(GamepadSlot&)>& aFn) {
mSharedState.RunWithLock([&](GamepadSystemState* p) {
// SECURITY BOUNDARY -- `GamepadSystemState* p` is a local copy of the
// shared memory (it is not a live copy), so after this validation we can
// trust the values inside of it
ValidateGamepadSystemState(p);
GamepadSlot* foundSlot = nullptr;
for (auto& slot : p->gamepadSlots) {
if (slot.handle == aHandle) {
foundSlot = &slot;
break;
}
}
if (!foundSlot) {
return;
}
aFn(*foundSlot);
p->changeId = mChangeId;
++mChangeId;
});
TriggerEvents();
}
void TriggerEvents() {
for (auto& x : mBroadcastEventHandles) {
MOZ_ALWAYS_TRUE(::SetEvent(x.eventHandle.Get()));
}
}
uint64_t mChangeId;
SharedState mSharedState;
Vector<BroadcastEventHandle> mBroadcastEventHandles;
};
//////////// Everything below this line is Pimpl boilerplate ///////////////////
// static
Maybe<GamepadStateBroadcaster> GamepadStateBroadcaster::Create() {
UniquePtr<Impl> impl = Impl::Create();
if (!impl) {
return Nothing{};
}
return Some(GamepadStateBroadcaster(std::move(impl)));
}
GamepadStateBroadcaster::~GamepadStateBroadcaster() = default;
bool GamepadStateBroadcaster::AddReceiverAndGenerateRemoteInfo(
const mozilla::ipc::IProtocol* aActor,
GamepadStateBroadcastReceiverInfo* aOut) {
return mImpl->AddReceiverAndGenerateRemoteInfo(aActor, aOut);
}
void GamepadStateBroadcaster::RemoveReceiver(
const mozilla::ipc::IProtocol* aActor) {
mImpl->RemoveReceiver(aActor);
}
void GamepadStateBroadcaster::AddGamepad(
GamepadHandle aHandle, const char* aID, GamepadMappingType aMapping,
GamepadHand aHand, uint32_t aNumButtons, uint32_t aNumAxes,
uint32_t aNumHaptics, uint32_t aNumLights, uint32_t aNumTouches) {
mImpl->AddGamepad(aHandle, aID, aMapping, aHand, aNumButtons, aNumAxes,
aNumHaptics, aNumLights, aNumTouches);
}
void GamepadStateBroadcaster::RemoveGamepad(GamepadHandle aHandle) {
mImpl->RemoveGamepad(aHandle);
}
void GamepadStateBroadcaster::NewAxisMoveEvent(GamepadHandle aHandle,
uint32_t aAxis, double aValue) {
mImpl->NewAxisMoveEvent(aHandle, aAxis, aValue);
}
void GamepadStateBroadcaster::NewButtonEvent(GamepadHandle aHandle,
uint32_t aButton, bool aPressed,
bool aTouched, double aValue) {
mImpl->NewButtonEvent(aHandle, aButton, aPressed, aTouched, aValue);
}
void GamepadStateBroadcaster::NewLightIndicatorTypeEvent(
GamepadHandle aHandle, uint32_t aLight, GamepadLightIndicatorType aType) {
mImpl->NewLightIndicatorTypeEvent(aHandle, aLight, aType);
}
void GamepadStateBroadcaster::NewPoseEvent(GamepadHandle aHandle,
const GamepadPoseState& aState) {
mImpl->NewPoseEvent(aHandle, aState);
}
void GamepadStateBroadcaster::NewMultiTouchEvent(
GamepadHandle aHandle, uint32_t aTouchArrayIndex,
const GamepadTouchState& aState) {
mImpl->NewMultiTouchEvent(aHandle, aTouchArrayIndex, aState);
}
void GamepadStateBroadcaster::SendTestCommand(uint32_t aCommandId) {
mImpl->SendTestCommand(aCommandId);
}
GamepadStateBroadcaster::GamepadStateBroadcaster(
GamepadStateBroadcaster&& aOther) = default;
GamepadStateBroadcaster& GamepadStateBroadcaster::operator=(
GamepadStateBroadcaster&& aOther) = default;
GamepadStateBroadcaster::GamepadStateBroadcaster() = default;
GamepadStateBroadcaster::GamepadStateBroadcaster(UniquePtr<Impl> aImpl)
: mImpl(std::move(aImpl)) {}
} // namespace mozilla::dom

View File

@ -1,100 +0,0 @@
/* -*- 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/. */
#ifndef GAMEPAD_DOM_GAMEPADSTATELAYOUT_H_
#define GAMEPAD_DOM_GAMEPADSTATELAYOUT_H_
#include "mozilla/dom/Gamepad.h"
#include "mozilla/dom/GamepadBinding.h"
#include "mozilla/dom/GamepadHandle.h"
#include "mozilla/dom/GamepadLightIndicatorBinding.h"
#include "mozilla/dom/GamepadPoseState.h"
#include "mozilla/dom/GamepadTouchState.h"
#include "mozilla/Array.h"
#include <bitset>
#include <inttypes.h>
namespace mozilla::dom {
// Define the shared memory and reasonable maximums for gamepads
constexpr size_t kMaxGamepadIdLength = 32;
constexpr size_t kMaxGamepads = 8;
// These 2 values from from the w3 spec:
// https://dvcs.w3.org/hg/gamepad/raw-file/default/gamepad.html#remapping
constexpr size_t kMaxButtonsPerGamepad = kStandardGamepadButtons;
constexpr size_t kMaxAxesPerGamepad = kStandardGamepadAxes;
constexpr size_t kMaxHapticsPerGamepad = 0; // We don't support haptics yet
constexpr size_t kMaxLightsPerGamepad = 2;
constexpr size_t kMaxNumMultiTouches = 2;
// CAUTION: You must update ValidateGamepadSystemState() if you change
// any of these structures
struct GamepadProperties {
Array<char, kMaxGamepadIdLength + 1> id{};
GamepadMappingType mapping{};
GamepadHand hand{};
uint32_t numAxes{};
uint32_t numButtons{};
uint32_t numHaptics{};
uint32_t numLights{};
uint32_t numTouches{};
};
// It is important that the default values for these members matches the
// default values for the JS objects. When a new gamepad is added, its state
// will be compared to a default GamepadValues object to determine what events
// are needed.
struct GamepadValues {
Array<double, kMaxAxesPerGamepad> axes{};
Array<double, kMaxButtonsPerGamepad> buttonValues{};
Array<GamepadLightIndicatorType, kMaxLightsPerGamepad> lights{};
Array<GamepadTouchState, kMaxNumMultiTouches> touches{};
GamepadPoseState pose{};
std::bitset<kMaxButtonsPerGamepad> buttonPressedBits;
std::bitset<kMaxButtonsPerGamepad> buttonTouchedBits;
};
struct GamepadSlot {
GamepadHandle handle{};
GamepadProperties props{};
GamepadValues values{};
};
struct GamepadSystemState {
Array<GamepadSlot, kMaxGamepads> gamepadSlots{};
uint64_t changeId{};
uint32_t testCommandId{};
bool testCommandTrigger{};
};
static void ValidateGamepadSystemState(GamepadSystemState* p) {
for (auto& slot : p->gamepadSlots) {
// Check that id is a null-terminated string
bool hasNull = false;
for (char c : slot.props.id) {
if (c == 0) {
hasNull = true;
break;
}
}
MOZ_RELEASE_ASSERT(hasNull);
MOZ_RELEASE_ASSERT(slot.props.mapping < GamepadMappingType::EndGuard_);
MOZ_RELEASE_ASSERT(slot.props.hand < GamepadHand::EndGuard_);
MOZ_RELEASE_ASSERT(slot.props.numAxes <= kMaxAxesPerGamepad);
MOZ_RELEASE_ASSERT(slot.props.numButtons <= kMaxButtonsPerGamepad);
MOZ_RELEASE_ASSERT(slot.props.numHaptics <= kMaxHapticsPerGamepad);
MOZ_RELEASE_ASSERT(slot.props.numLights <= kMaxLightsPerGamepad);
MOZ_RELEASE_ASSERT(slot.props.numTouches <= kMaxNumMultiTouches);
}
}
} // namespace mozilla::dom
#endif // GAMEPAD_DOM_GAMEPADSTATELAYOUT_H_

View File

@ -1,300 +0,0 @@
/* -*- 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/dom/GamepadStateReceiver.h"
#include "GamepadStateLayout.h"
#include "GamepadWindowsUtil.h"
#include "mozilla/dom/GamepadStateBroadcastReceiverInfo.h"
#include "mozilla/dom/GamepadEventTypes.h"
#include "mozilla/dom/SynchronizedSharedMemory.h"
#include "mozilla/ipc/ProtocolUtils.h"
#include "prthread.h"
#include <atomic>
#include <inttypes.h>
#include <windows.h>
namespace mozilla::dom {
using SharedState = SynchronizedSharedMemory<GamepadSystemState>;
class GamepadStateReceiver::Impl {
public:
static UniquePtr<Impl> Create(
const GamepadStateBroadcastReceiverInfo& aReceiverInfo) {
Maybe<SharedState> sharedState =
SharedState::CreateFromRemoteInfo(aReceiverInfo.sharedMemoryInfo());
UniqueHandle<NTEventHandleTraits> eventHandle(
reinterpret_cast<HANDLE>(aReceiverInfo.eventHandle()));
if (!sharedState || !eventHandle) {
return nullptr;
}
return UniquePtr<Impl>(
new Impl(std::move(*sharedState), std::move(eventHandle)));
}
bool StartMonitoringThread(
const std::function<void(const GamepadChangeEvent&)>& aMonitorFn,
const std::function<void(uint32_t)>& aTestCommandFn) {
MOZ_ASSERT(!mMonitorThread);
mMonitorFn = aMonitorFn;
mTestCommandFn = aTestCommandFn;
// Every time the thread wakes up from an event, it checks this before
// it does anything else. The thread exits when this is `true`
mStopMonitoring.store(false, std::memory_order_release);
mMonitorThread = PR_CreateThread(
PR_USER_THREAD,
[](void* p) { static_cast<Impl*>(p)->MonitoringThread(); }, this,
PR_PRIORITY_NORMAL, PR_GLOBAL_THREAD, PR_JOINABLE_THREAD, 0);
return !!mMonitorThread;
}
void StopMonitoringThread() {
MOZ_ASSERT(mMonitorThread);
/// Every time the thread wakes up from an event, it checks this before
// it does anything else. The thread exits when this is `true`
mStopMonitoring.store(true, std::memory_order_release);
// Wake the thread up with the event, causing it to exit
MOZ_ALWAYS_TRUE(::SetEvent(mEventHandle.Get()));
MOZ_ALWAYS_TRUE(PR_JoinThread(mMonitorThread) == PR_SUCCESS);
mMonitorThread = nullptr;
mMonitorFn = nullptr;
mTestCommandFn = nullptr;
}
// Disallow copy/move
Impl(const Impl&) = delete;
Impl& operator=(const Impl&) = delete;
Impl(Impl&&) = delete;
Impl& operator=(Impl&&) = delete;
~Impl() {
if (mMonitorThread) {
MOZ_ASSERT(false,
"GamepadStateReceiver::~Impl() was called without "
"calling StopMonitoringThread().");
StopMonitoringThread();
}
}
private:
explicit Impl(SharedState aSharedState,
UniqueHandle<NTEventHandleTraits> aEventHandle)
: mSharedState(std::move(aSharedState)),
mEventHandle(std::move(aEventHandle)),
mMonitorThread(nullptr) {}
// This compares two GamepadValues structures for a single gamepad and
// generates events for any detected differences.
// Generally we are either comparing a known gamepad to its last known state,
// or we are comparing a new gamepad against the default state
void DiffGamepadValues(
GamepadHandle handle, const GamepadProperties& props,
const GamepadValues& curValues, const GamepadValues& newValues,
const std::function<void(const GamepadChangeEvent&)>& aFn) {
// Diff axes
for (uint32_t i = 0; i < props.numAxes; ++i) {
if (curValues.axes[i] != newValues.axes[i]) {
GamepadAxisInformation axisInfo(i, newValues.axes[i]);
GamepadChangeEvent e(handle, axisInfo);
aFn(e);
}
}
// Diff buttons
for (uint32_t i = 0; i < props.numButtons; ++i) {
if ((curValues.buttonValues[i] != newValues.buttonValues[i]) ||
(curValues.buttonPressedBits[i] != newValues.buttonPressedBits[i]) ||
(curValues.buttonTouchedBits[i] != newValues.buttonTouchedBits[i])) {
GamepadButtonInformation buttonInfo(i, newValues.buttonValues[i],
newValues.buttonPressedBits[i],
newValues.buttonTouchedBits[i]);
GamepadChangeEvent e(handle, buttonInfo);
aFn(e);
}
}
// Diff indictator lights
for (uint32_t i = 0; i < props.numLights; ++i) {
if (curValues.lights[i] != newValues.lights[i]) {
GamepadLightIndicatorTypeInformation lightInfo(i, newValues.lights[i]);
GamepadChangeEvent e(handle, lightInfo);
aFn(e);
}
}
// Diff multi-touch states
for (uint32_t i = 0; i < props.numTouches; ++i) {
if (curValues.touches[i] != newValues.touches[i]) {
GamepadTouchInformation touchInfo(i, newValues.touches[i]);
GamepadChangeEvent e(handle, touchInfo);
aFn(e);
}
}
// Diff poses
if (curValues.pose != newValues.pose) {
GamepadPoseInformation poseInfo(newValues.pose);
GamepadChangeEvent e(handle, poseInfo);
aFn(e);
}
}
// Compare two gamepad slots and generate events based on the differences
//
// This is generally used to compare the old and new state of a single slot.
// If the same gamepad is still in the slot from last check, we just need
// to diff the gamepad's values.
//
// If a different gamepad is now in the slot, we need to unregister the old
// one, register the new one, and generate events for every value that is
// different than the default "new gamepad" state.
//
void DiffGamepadSlots(
uint32_t changeId, const GamepadSlot& curState,
const GamepadSlot& newState,
const std::function<void(const GamepadChangeEvent&)>& aFn) {
if (curState.handle == newState.handle) {
if (newState.handle != GamepadHandle{}) {
// Same gamepad is plugged in as last time - Just diff values
DiffGamepadValues(newState.handle, newState.props, curState.values,
newState.values, aFn);
}
return;
}
// The gamepad in this slot has changed.
// If there was previously a gamepad in this slot, remove it
if (curState.handle != GamepadHandle{}) {
GamepadChangeEvent e(curState.handle, GamepadRemoved{});
aFn(e);
}
// If there is a gamepad in it now, register it
if (newState.handle != GamepadHandle{}) {
GamepadAdded gamepadInfo(
NS_ConvertUTF8toUTF16(nsDependentCString(&newState.props.id[0])),
newState.props.mapping, newState.props.hand, 0,
newState.props.numButtons, newState.props.numAxes,
newState.props.numHaptics, newState.props.numLights,
newState.props.numTouches);
GamepadChangeEvent e(newState.handle, gamepadInfo);
aFn(e);
// Since the gamepad is new, we diff its values against a
// default-constructed GamepadValues structure
DiffGamepadValues(newState.handle, newState.props, GamepadValues{},
newState.values, aFn);
}
}
void MonitoringThread() {
while (true) {
// If the other side crashes or something goes very wrong, we're probably
// about to be destroyed anyhow. Just quit the thread and await our
// inevitable doom
if (::WaitForSingleObject(mEventHandle.Get(), INFINITE) !=
WAIT_OBJECT_0) {
break;
}
// First check if the signal is from StopMonitoring telling us we're done
if (mStopMonitoring.load(std::memory_order_acquire)) {
break;
}
// Read the shared memory
GamepadSystemState newSystemState;
mSharedState.RunWithLock(
[&](GamepadSystemState* p) { newSystemState = *p; });
// SECURITY BOUNDARY -- After this validation, we can trust newSystemState
ValidateGamepadSystemState(&newSystemState);
if (mGamepadSystemState.changeId != newSystemState.changeId) {
// Diff the state of each gamepad slot from the previous read
for (size_t i = 0; i < kMaxGamepads; ++i) {
DiffGamepadSlots(newSystemState.changeId,
mGamepadSystemState.gamepadSlots[i],
newSystemState.gamepadSlots[i], mMonitorFn);
}
if (mGamepadSystemState.testCommandTrigger !=
newSystemState.testCommandTrigger) {
if (mTestCommandFn) {
mTestCommandFn(newSystemState.testCommandId);
}
}
// Save the current read for the next diff
mGamepadSystemState = newSystemState;
}
}
}
SharedState mSharedState;
UniqueHandle<NTEventHandleTraits> mEventHandle;
GamepadSystemState mGamepadSystemState;
std::atomic_bool mStopMonitoring;
std::function<void(const GamepadChangeEvent&)> mMonitorFn;
std::function<void(uint32_t)> mTestCommandFn;
PRThread* mMonitorThread;
};
//////////// Everything below this line is Pimpl boilerplate ///////////////////
// static
Maybe<GamepadStateReceiver> GamepadStateReceiver::Create(
const GamepadStateBroadcastReceiverInfo& aReceiverInfo) {
UniquePtr<Impl> impl = Impl::Create(aReceiverInfo);
if (!impl) {
return Nothing{};
}
return Some(GamepadStateReceiver(std::move(impl)));
}
GamepadStateReceiver::~GamepadStateReceiver() = default;
GamepadStateReceiver::GamepadStateReceiver(GamepadStateReceiver&& aOther) =
default;
GamepadStateReceiver& GamepadStateReceiver::operator=(
GamepadStateReceiver&& aOther) = default;
GamepadStateReceiver::GamepadStateReceiver() = default;
GamepadStateReceiver::GamepadStateReceiver(UniquePtr<Impl> aImpl)
: mImpl(std::move(aImpl)) {}
bool GamepadStateReceiver::StartMonitoringThread(
const std::function<void(const GamepadChangeEvent&)>& aFn) {
return mImpl->StartMonitoringThread(aFn, nullptr);
}
bool GamepadStateReceiver::StartMonitoringThreadForTesting(
const std::function<void(const GamepadChangeEvent&)>& aMonitorFn,
const std::function<void(uint32_t)>& aTestCommandFn) {
return mImpl->StartMonitoringThread(aMonitorFn, aTestCommandFn);
}
void GamepadStateReceiver::StopMonitoringThread() {
mImpl->StopMonitoringThread();
}
} // namespace mozilla::dom

View File

@ -1,156 +0,0 @@
/* -*- 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/. */
#ifndef DOM_GAMEPAD_GAMEPADWINDOWSUTIL_H_
#define DOM_GAMEPAD_GAMEPADWINDOWSUTIL_H_
#include "mozilla/Assertions.h"
#include <utility>
#include <windows.h>
namespace mozilla::dom {
// UniqueHandle - A generic RAII wrapper for handle-like objects
//
// A type that behaves like UniquePtr, but for arbitrary handle types.
// The type, HandleType, only needs to be copyable (like all C primitive types
// and structures). The `Traits` template parameter will define the `HandleType`
// and the following 3 or 4 operations on the basic type:
//
// static HandleType Invalid()
// Must return an instance of the object that is considered invalid
// according to `IsValid()`. Note that UniqueHandle doesn't assume that
// there is a sentinal value that is invalid -- It only cares that
// IsValid(Invalid()) is false
//
// static bool IsValid(HandleType)
// Returns true if the handle is valid, false otherwise
//
// static void Close(HandleType)
// Called on destruction to close the handle (only called if IsValid())
//
// static bool IsEqual(HandleType, HandleType)
// OPTIONAL -- If you want the UniqueHandle type to support == and !=
// operators, implement this. It doesn't affect anything else.
//
// (Note that the functions can take the handle by value or by const-ref)
//
// # Example
//
// struct FdHandleTraits {
// using HandleType = int;
// static HandleType Invalid() { return -1; }
// static void Close(HandleType handle) { close(handle); }
// static bool IsValid(HandleType handle) { return handle >= 0; }
// static bool IsEqual(HandleType a, HandleType b) { return a == b; }
// };
//
// UniqueHandle<FdHandleTraits> uniqueFile(open(...));
//
template <typename Traits>
class UniqueHandle {
public:
using HandleType = typename Traits::HandleType;
UniqueHandle() : mHandle(Traits::Invalid()) {
MOZ_ASSERT(!Traits::IsValid(mHandle));
}
explicit UniqueHandle(HandleType t) : mHandle(std::move(t)) {
MOZ_ASSERT(!Traits::IsValid(Traits::Invalid()));
}
UniqueHandle(UniqueHandle&& other) : UniqueHandle() { swap(*this, other); }
~UniqueHandle() {
if (Traits::IsValid(mHandle)) {
Traits::Close(mHandle);
mHandle = Traits::Invalid();
}
}
UniqueHandle& operator=(UniqueHandle&& other) {
UniqueHandle temp(std::move(other));
swap(*this, temp);
return *this;
}
HandleType Release() {
using std::swap;
HandleType result = Traits::Invalid();
swap(result, mHandle);
return result;
}
explicit operator bool() const { return Traits::IsValid(mHandle); }
HandleType Get() const { return mHandle; }
friend void swap(UniqueHandle& a, UniqueHandle& b) {
// Well-behaved types *should* properly handle self-swap, but let's be
// defensive just in case
if (&a != &b) {
using std::swap;
swap(a.mHandle, b.mHandle);
}
}
// If the traits don't provide an isEqual() function, this will get SFINAE'd
// out and equality tests won't be supported
template <typename = decltype(Traits::IsEqual(Traits::Invalid(),
Traits::Invalid()))>
friend bool operator==(const UniqueHandle& a, const UniqueHandle& b) {
return Traits::IsEqual(a.mHandle, b.mHandle);
}
template <typename = decltype(Traits::IsEqual(Traits::Invalid(),
Traits::Invalid()))>
friend bool operator!=(const UniqueHandle& a, const UniqueHandle& b) {
return !(a == b);
}
UniqueHandle(const UniqueHandle&) = delete;
UniqueHandle& operator=(const UniqueHandle&) = delete;
private:
HandleType mHandle;
};
// UniqueHandle trait for NT Kernel handles.
// Has `Tag` to create a strong type for each kind of NT handle.
template <typename Tag>
struct NTKernelHandleTraits {
using HandleType = HANDLE;
static HandleType Invalid() { return nullptr; }
static bool IsValid(HandleType handle) { return !!handle; }
static void Close(HandleType handle) {
MOZ_ALWAYS_TRUE(::CloseHandle(handle));
}
static bool IsEqual(const HandleType& a, const HandleType& b) {
return a == b;
}
};
// Create strong handle traits for several different NT handle types
struct NTMutexHandleTag {};
struct NTEventHandleTag {};
struct NTFileMappingHandleTag {};
using NTMutexHandleTraits = NTKernelHandleTraits<NTMutexHandleTag>;
using NTEventHandleTraits = NTKernelHandleTraits<NTEventHandleTag>;
using NTFileMappingHandleTraits = NTKernelHandleTraits<NTFileMappingHandleTag>;
// UniqueHandle trait for NT file mapping
struct NTFileViewHandleTraits {
using HandleType = HANDLE;
static void* Invalid() { return nullptr; }
static bool IsValid(void* p) { return !!p; }
static void Close(void* p) { MOZ_ALWAYS_TRUE(::UnmapViewOfFile(p)); }
static bool IsEqual(const void* a, const void* b) { return a == b; }
};
} // namespace mozilla::dom
#endif // DOM_GAMEPAD_GAMEPADWINDOWSUTIL_H_

View File

@ -1,213 +0,0 @@
/* -*- 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/. */
// This is the implementation of SynchronizedSharedMemory for Windows.
#include "mozilla/dom/SynchronizedSharedMemory.h"
#include "mozilla/dom/SynchronizedSharedMemoryRemoteInfo.h"
#include "mozilla/ipc/ProtocolUtils.h"
#include "GamepadWindowsUtil.h"
#include <utility>
#include <windows.h>
namespace mozilla::dom {
// This Impl class is where most of the actual logic is for creating/using
// the shared memory is located. The public member functions of
// SynchronizedSharedMemoryDetail mostly just pass the call through to this.
class SynchronizedSharedMemoryDetail::Impl {
public:
static UniquePtr<Impl> CreateNew(uint32_t aSize) {
UniqueHandle<NTMutexHandleTraits> mutex(::CreateMutex(
nullptr /*no ACL*/, FALSE /*not owned*/, nullptr /*no name*/));
if (!mutex) {
return nullptr;
}
UniqueHandle<NTFileMappingHandleTraits> fileMapping(
::CreateFileMapping(INVALID_HANDLE_VALUE /*memory-only*/,
nullptr /*no ACL*/, PAGE_READWRITE, 0 /*sizeHigh*/,
aSize /*sizeLow*/, nullptr /*no name*/));
if (!fileMapping) {
return nullptr;
}
UniqueHandle<NTFileViewHandleTraits> sharedPtr(::MapViewOfFile(
fileMapping.Get(), FILE_MAP_ALL_ACCESS, 0 /*offsetHigh*/,
0 /*offsetLow*/, 0 /*Map entire region*/));
if (!sharedPtr) {
return nullptr;
}
return UniquePtr<Impl>(new Impl(std::move(mutex), std::move(fileMapping),
std::move(sharedPtr)));
}
static UniquePtr<Impl> CreateFromRemote(
const SynchronizedSharedMemoryRemoteInfo& aRemoteCreationInfo) {
UniqueHandle<NTMutexHandleTraits> mutex(
reinterpret_cast<HANDLE>(aRemoteCreationInfo.mutexHandle()));
UniqueHandle<NTFileMappingHandleTraits> fileMapping(
reinterpret_cast<HANDLE>(aRemoteCreationInfo.sharedFileHandle()));
if (!mutex || !fileMapping) {
return nullptr;
}
UniqueHandle<NTFileViewHandleTraits> sharedPtr(::MapViewOfFile(
fileMapping.Get(), FILE_MAP_ALL_ACCESS, 0 /*offsetHigh*/,
0 /*offsetLow*/, 0 /*Map entire region*/));
if (!sharedPtr) {
return nullptr;
}
return UniquePtr<Impl>(new Impl(std::move(mutex), std::move(fileMapping),
std::move(sharedPtr)));
}
~Impl() {
MOZ_ASSERT(mMutex);
MOZ_ASSERT(mFileMapping);
MOZ_ASSERT(mSharedPtr);
}
bool GenerateRemoteInfo(const mozilla::ipc::IProtocol* aActor,
SynchronizedSharedMemoryRemoteInfo* aOut) {
MOZ_ASSERT(mMutex);
MOZ_ASSERT(mFileMapping);
DWORD targetPID = aActor ? aActor->OtherPid() : ::GetCurrentProcessId();
HANDLE remoteMutexHandle = nullptr;
if (!mozilla::ipc::DuplicateHandle(mMutex.Get(), targetPID,
&remoteMutexHandle, 0,
DUPLICATE_SAME_ACCESS)) {
return false;
}
HANDLE remoteSharedFileHandle = nullptr;
if (!mozilla::ipc::DuplicateHandle(mFileMapping.Get(), targetPID,
&remoteSharedFileHandle, 0,
DUPLICATE_SAME_ACCESS)) {
// There's no reasonable way to prevent leaking remoteMutexHandle here,
// since we don't own it
return false;
}
MOZ_ASSERT(remoteMutexHandle);
MOZ_ASSERT(remoteSharedFileHandle);
(*aOut) = SynchronizedSharedMemoryRemoteInfo{
reinterpret_cast<WindowsHandle>(remoteMutexHandle),
reinterpret_cast<WindowsHandle>(remoteSharedFileHandle),
};
return true;
}
void LockMutex() {
MOZ_ALWAYS_TRUE(::WaitForSingleObject(mMutex.Get(), INFINITE) !=
WAIT_FAILED);
}
void UnlockMutex() { MOZ_ALWAYS_TRUE(::ReleaseMutex(mMutex.Get())); }
void* GetPtr() const { return mSharedPtr.Get(); }
// Disallow copying/moving
Impl(const Impl&) = delete;
Impl(Impl&&) = delete;
Impl& operator=(const Impl&) = delete;
Impl& operator=(Impl&&) = delete;
private:
Impl(UniqueHandle<NTMutexHandleTraits> aMutex,
UniqueHandle<NTFileMappingHandleTraits> aFileMapping,
UniqueHandle<NTFileViewHandleTraits> aSharedPtr)
: mMutex(std::move(aMutex)),
mFileMapping(std::move(aFileMapping)),
mSharedPtr(std::move(aSharedPtr)) {
MOZ_ASSERT(mMutex);
MOZ_ASSERT(mFileMapping);
MOZ_ASSERT(mSharedPtr);
}
UniqueHandle<NTMutexHandleTraits> mMutex;
UniqueHandle<NTFileMappingHandleTraits> mFileMapping;
UniqueHandle<NTFileViewHandleTraits> mSharedPtr;
};
//////////// Everything below this line is Pimpl boilerplate ///////////////////
// static
Maybe<SynchronizedSharedMemoryDetail> SynchronizedSharedMemoryDetail::CreateNew(
uint32_t aSize) {
MOZ_RELEASE_ASSERT(aSize);
UniquePtr<Impl> impl = Impl::CreateNew(aSize);
if (!impl) {
return Nothing{};
}
return Some(SynchronizedSharedMemoryDetail(std::move(impl)));
}
void SynchronizedSharedMemoryDetail::LockMutex() {
MOZ_RELEASE_ASSERT(mImpl);
mImpl->LockMutex();
}
void SynchronizedSharedMemoryDetail::UnlockMutex() {
MOZ_RELEASE_ASSERT(mImpl);
mImpl->UnlockMutex();
}
void* SynchronizedSharedMemoryDetail::GetPtr() const {
MOZ_RELEASE_ASSERT(mImpl);
return mImpl->GetPtr();
}
// static
Maybe<SynchronizedSharedMemoryDetail>
SynchronizedSharedMemoryDetail::CreateFromRemoteInfo(
const SynchronizedSharedMemoryRemoteInfo& aIPCInfo) {
UniquePtr<Impl> impl = Impl::CreateFromRemote(aIPCInfo);
if (!impl) {
return Nothing{};
}
return Some(SynchronizedSharedMemoryDetail(std::move(impl)));
}
bool SynchronizedSharedMemoryDetail::GenerateRemoteInfo(
const mozilla::ipc::IProtocol* aActor,
SynchronizedSharedMemoryRemoteInfo* aOut) {
MOZ_RELEASE_ASSERT(mImpl);
return mImpl->GenerateRemoteInfo(aActor, aOut);
}
bool SynchronizedSharedMemoryDetail::GenerateTestRemoteInfo(
SynchronizedSharedMemoryRemoteInfo* aOut) {
MOZ_RELEASE_ASSERT(mImpl);
return mImpl->GenerateRemoteInfo(nullptr, aOut);
}
SynchronizedSharedMemoryDetail::SynchronizedSharedMemoryDetail() = default;
SynchronizedSharedMemoryDetail::~SynchronizedSharedMemoryDetail() = default;
SynchronizedSharedMemoryDetail::SynchronizedSharedMemoryDetail(
UniquePtr<Impl> aImpl)
: mImpl(std::move(aImpl)) {
MOZ_ASSERT(mImpl);
}
SynchronizedSharedMemoryDetail::SynchronizedSharedMemoryDetail(
SynchronizedSharedMemoryDetail&&) = default;
SynchronizedSharedMemoryDetail& SynchronizedSharedMemoryDetail::operator=(
SynchronizedSharedMemoryDetail&&) = default;
} // namespace mozilla::dom

View File

@ -1,16 +0,0 @@
/* 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/. */
using mozilla::WindowsHandle from "mozilla/ipc/IPCTypes.h";
namespace mozilla {
namespace dom {
struct SynchronizedSharedMemoryRemoteInfo {
WindowsHandle mutexHandle;
WindowsHandle sharedFileHandle;
};
} // namespace dom
} // namespace mozilla

View File

@ -1166,16 +1166,6 @@ BackgroundParentImpl::AllocPGamepadEventChannelParent() {
return dom::GamepadEventChannelParent::Create();
}
mozilla::ipc::IPCResult
BackgroundParentImpl::RecvPGamepadEventChannelConstructor(
PGamepadEventChannelParent* actor) {
if (!static_cast<mozilla::dom::GamepadEventChannelParent*>(actor)
->ActorInit()) {
return IPC_FAIL(this, "ActorInit failed");
}
return IPC_OK();
}
already_AddRefed<dom::PGamepadTestChannelParent>
BackgroundParentImpl::AllocPGamepadTestChannelParent() {
return dom::GamepadTestChannelParent::Create();

View File

@ -328,9 +328,6 @@ class BackgroundParentImpl : public PBackgroundParent,
already_AddRefed<PGamepadEventChannelParent> AllocPGamepadEventChannelParent()
override;
mozilla::ipc::IPCResult RecvPGamepadEventChannelConstructor(
PGamepadEventChannelParent* actor) override;
already_AddRefed<PGamepadTestChannelParent> AllocPGamepadTestChannelParent()
override;