bug 604039 - Add DOM Gamepad APIs. r=smaug

--HG--
extra : rebase_source : ffffdc4549da1b25ea263b623c05ae1afb3d46a0
This commit is contained in:
Ted Mielczarek 2011-08-03 14:12:08 -04:00
parent 72d26c093a
commit bc92001c0b
44 changed files with 1684 additions and 6 deletions

View File

@ -214,6 +214,9 @@
@BINPATH@/components/dom_xbl.xpt
@BINPATH@/components/dom_xpath.xpt
@BINPATH@/components/dom_xul.xpt
#ifdef MOZ_GAMEPAD
@BINPATH@/components/dom_gamepad.xpt
#endif
@BINPATH@/components/downloads.xpt
@BINPATH@/components/editor.xpt
@BINPATH@/components/embed_base.xpt

View File

@ -5927,6 +5927,37 @@ if test -n "$MOZ_ANGLE_RENDERER" -a -z "$CROSS_COMPILE"; then
MOZ_D3DCOMPILER_DLL=D3DCompiler_$MOZ_D3DX9_VERSION.dll
fi
dnl ========================================================
dnl Gamepad support
dnl ========================================================
MOZ_GAMEPAD=
MOZ_GAMEPAD_BACKEND=stub
# Gamepad DOM is built on supported platforms by default.
case "$OS_TARGET" in
Darwin|WINNT|Linux)
MOZ_GAMEPAD=1
;;
*)
;;
esac
MOZ_ARG_DISABLE_BOOL(gamepad,
[ --disable-gamepad Disable gamepad support],
MOZ_GAMEPAD=,
MOZ_GAMEPAD=1)
if test "$MOZ_GAMEPAD"; then
case "$OS_TARGET" in
*)
;;
esac
AC_DEFINE(MOZ_GAMEPAD)
fi
AC_SUBST(MOZ_GAMEPAD)
AC_SUBST(MOZ_GAMEPAD_BACKEND)
dnl ========================================================
dnl = Breakpad crash reporting (on by default on supported platforms)
dnl ========================================================

View File

@ -1895,6 +1895,13 @@ GK_ATOM(canplaythrough, "canplaythrough")
GK_ATOM(ratechange, "ratechange")
GK_ATOM(durationchange, "durationchange")
GK_ATOM(volumechange, "volumechange")
#ifdef MOZ_GAMEPAD
GK_ATOM(ongamepadbuttondown, "ongamepadbuttondown")
GK_ATOM(ongamepadbuttonup, "ongamepadbuttonup")
GK_ATOM(ongamepadaxismove, "ongamepadaxismove")
GK_ATOM(ongamepadconnected, "ongamepadconnected")
GK_ATOM(ongamepaddisconnected, "ongamepaddisconnected")
#endif
// Content property names
GK_ATOM(animationsProperty, "AnimationsProperty") // FrameAnimations*

View File

@ -754,6 +754,29 @@ NON_IDL_EVENT(MozScrolledAreaChanged,
EventNameType_None,
NS_SCROLLAREA_EVENT)
#ifdef MOZ_GAMEPAD
NON_IDL_EVENT(gamepadbuttondown,
NS_GAMEPAD_BUTTONDOWN,
EventNameType_None,
NS_EVENT_NULL)
NON_IDL_EVENT(gamepadbuttonup,
NS_GAMEPAD_BUTTONUP,
EventNameType_None,
NS_EVENT_NULL)
NON_IDL_EVENT(gamepadaxismove,
NS_GAMEPAD_AXISMOVE,
EventNameType_None,
NS_EVENT_NULL)
NON_IDL_EVENT(gamepadconnected,
NS_GAMEPAD_CONNECTED,
EventNameType_None,
NS_EVENT_NULL)
NON_IDL_EVENT(gamepaddisconnected,
NS_GAMEPAD_DISCONNECTED,
EventNameType_None,
NS_EVENT_NULL)
#endif
// Simple gesture events
NON_IDL_EVENT(MozSwipeGesture,
NS_SIMPLE_GESTURE_SWIPE,

View File

@ -21,6 +21,7 @@ EXPORTS = \
nsEventListenerManager.h \
nsDOMEventTargetHelper.h \
nsDOMEvent.h \
nsDOMGamepad.h \
nsDOMTouchEvent.h \
nsDOMUIEvent.h \
$(NULL)
@ -64,6 +65,12 @@ CPPSRCS = \
TextComposition.cpp \
$(NULL)
ifdef MOZ_GAMEPAD
CPPSRCS += \
nsDOMGamepad.cpp \
$(NULL)
endif
# we don't want the shared lib, but we want to force the creation of a static lib.
FORCE_STATIC_LIB = 1

View File

@ -0,0 +1,171 @@
/* 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 "nsDOMGamepad.h"
#include "nsDOMClassInfoID.h"
#include "nsIClassInfo.h"
#include "nsIXPCScriptable.h"
#include "nsTArray.h"
#include "nsContentUtils.h"
#include "nsVariant.h"
DOMCI_DATA(Gamepad, nsDOMGamepad)
NS_IMPL_ADDREF(nsDOMGamepad)
NS_IMPL_RELEASE(nsDOMGamepad)
NS_INTERFACE_MAP_BEGIN(nsDOMGamepad)
NS_INTERFACE_MAP_ENTRY(nsISupports)
NS_INTERFACE_MAP_ENTRY(nsIDOMGamepad)
NS_DOM_INTERFACE_MAP_ENTRY_CLASSINFO(Gamepad)
NS_INTERFACE_MAP_END
nsDOMGamepad::nsDOMGamepad(const nsAString& aID, uint32_t aIndex,
uint32_t aNumButtons, uint32_t aNumAxes)
: mID(aID),
mIndex(aIndex),
mConnected(true)
{
mButtons.InsertElementsAt(0, aNumButtons, 0);
mAxes.InsertElementsAt(0, aNumAxes, 0.0f);
}
/* readonly attribute DOMString id; */
NS_IMETHODIMP
nsDOMGamepad::GetId(nsAString& aID)
{
aID = mID;
return NS_OK;
}
NS_IMETHODIMP
nsDOMGamepad::GetIndex(uint32_t* aIndex)
{
*aIndex = mIndex;
return NS_OK;
}
void
nsDOMGamepad::SetIndex(uint32_t aIndex)
{
mIndex = aIndex;
}
void
nsDOMGamepad::SetConnected(bool aConnected)
{
mConnected = aConnected;
}
void
nsDOMGamepad::SetButton(uint32_t aButton, double aValue)
{
MOZ_ASSERT(aButton < mButtons.Length());
mButtons[aButton] = aValue;
}
void
nsDOMGamepad::SetAxis(uint32_t aAxis, double aValue)
{
MOZ_ASSERT(aAxis < mAxes.Length());
mAxes[aAxis] = aValue;
}
/* readonly attribute boolean connected; */
NS_IMETHODIMP
nsDOMGamepad::GetConnected(bool* aConnected)
{
*aConnected = mConnected;
return NS_OK;
}
/* readonly attribute nsIVariant buttons; */
NS_IMETHODIMP
nsDOMGamepad::GetButtons(nsIVariant** aButtons)
{
nsRefPtr<nsVariant> out = new nsVariant();
NS_ENSURE_STATE(out);
if (mButtons.Length() == 0) {
nsresult rv = out->SetAsEmptyArray();
NS_ENSURE_SUCCESS(rv, rv);
} else {
// Note: The resulting nsIVariant dupes both the array and its elements.
double* array = reinterpret_cast<double*>
(NS_Alloc(mButtons.Length() * sizeof(double)));
NS_ENSURE_TRUE(array, NS_ERROR_OUT_OF_MEMORY);
for (uint32_t i = 0; i < mButtons.Length(); ++i) {
array[i] = mButtons[i];
}
nsresult rv = out->SetAsArray(nsIDataType::VTYPE_DOUBLE,
nullptr,
mButtons.Length(),
reinterpret_cast<void*>(array));
NS_Free(array);
NS_ENSURE_SUCCESS(rv, rv);
}
*aButtons = out.forget().get();
return NS_OK;
}
/* readonly attribute nsIVariant axes; */
NS_IMETHODIMP
nsDOMGamepad::GetAxes(nsIVariant** aAxes)
{
nsRefPtr<nsVariant> out = new nsVariant();
NS_ENSURE_STATE(out);
if (mAxes.Length() == 0) {
nsresult rv = out->SetAsEmptyArray();
NS_ENSURE_SUCCESS(rv, rv);
} else {
// Note: The resulting nsIVariant dupes both the array and its elements.
double* array = reinterpret_cast<double*>
(NS_Alloc(mAxes.Length() * sizeof(double)));
NS_ENSURE_TRUE(array, NS_ERROR_OUT_OF_MEMORY);
for (uint32_t i = 0; i < mAxes.Length(); ++i) {
array[i] = mAxes[i];
}
nsresult rv = out->SetAsArray(nsIDataType::VTYPE_DOUBLE,
nullptr,
mAxes.Length(),
reinterpret_cast<void*>(array));
NS_Free(array);
NS_ENSURE_SUCCESS(rv, rv);
}
*aAxes = out.forget().get();
return NS_OK;
}
void
nsDOMGamepad::SyncState(nsDOMGamepad* aOther)
{
if (mButtons.Length() != aOther->mButtons.Length() ||
mAxes.Length() != aOther->mAxes.Length()) {
return;
}
mConnected = aOther->mConnected;
for (uint32_t i = 0; i < mButtons.Length(); ++i) {
mButtons[i] = aOther->mButtons[i];
}
for (uint32_t i = 0; i < mAxes.Length(); ++i) {
mAxes[i] = aOther->mAxes[i];
}
}
already_AddRefed<nsDOMGamepad>
nsDOMGamepad::Clone()
{
nsRefPtr<nsDOMGamepad> out =
new nsDOMGamepad(mID, mIndex, mButtons.Length(), mAxes.Length());
out->SyncState(this);
return out.forget();
}

View File

@ -0,0 +1,49 @@
/* 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 nsDomGamepad_h
#define nsDomGamepad_h
#include "mozilla/StandardInteger.h"
#include "nsIDOMGamepad.h"
#include "nsString.h"
#include "nsCOMPtr.h"
#include "nsTArray.h"
class nsDOMGamepad : public nsIDOMGamepad
{
public:
nsDOMGamepad(const nsAString& aID, uint32_t aIndex,
uint32_t aNumButtons, uint32_t aNumAxes);
NS_DECL_ISUPPORTS
NS_DECL_NSIDOMGAMEPAD
nsDOMGamepad();
void SetConnected(bool aConnected);
void SetButton(uint32_t aButton, double aValue);
void SetAxis(uint32_t aAxis, double aValue);
void SetIndex(uint32_t aIndex);
// Make the state of this gamepad equivalent to other.
void SyncState(nsDOMGamepad* other);
// Return a new nsDOMGamepad containing the same data as this object.
already_AddRefed<nsDOMGamepad> Clone();
private:
virtual ~nsDOMGamepad() {}
protected:
nsString mID;
uint32_t mIndex;
// true if this gamepad is currently connected.
bool mConnected;
// Current state of buttons, axes.
nsTArray<double> mButtons;
nsTArray<double> mAxes;
};
#endif // nsDomGamepad_h

View File

@ -339,6 +339,14 @@ nsEventListenerManager::AddEventListenerInternal(
#endif
window->SetHasMouseEnterLeaveEventListeners();
}
#ifdef MOZ_GAMEPAD
} else if (aType >= NS_GAMEPAD_START &&
aType <= NS_GAMEPAD_END) {
nsPIDOMWindow* window = GetInnerWindowForTarget();
if (window) {
window->SetHasGamepadEventListener();
}
#endif
}
}

View File

@ -230,6 +230,9 @@
#include "nsIControllers.h"
#include "nsISelection.h"
#include "nsIBoxObject.h"
#ifdef MOZ_GAMEPAD
#include "nsIDOMGamepad.h"
#endif
#ifdef MOZ_XUL
#include "nsITreeSelection.h"
#include "nsITreeContentView.h"
@ -1057,6 +1060,11 @@ static nsDOMClassInfoData sClassInfoData[] = {
NS_DEFINE_CLASSINFO_DATA(TouchEvent, nsEventSH,
DOM_DEFAULT_SCRIPTABLE_FLAGS)
#ifdef MOZ_GAMEPAD
NS_DEFINE_CLASSINFO_DATA(Gamepad, nsDOMGenericSH,
DOM_DEFAULT_SCRIPTABLE_FLAGS)
#endif
NS_DEFINE_CLASSINFO_DATA(MozCSSKeyframeRule, nsDOMGenericSH,
DOM_DEFAULT_SCRIPTABLE_FLAGS)
NS_DEFINE_CLASSINFO_DATA(MozCSSKeyframesRule, nsDOMGenericSH,
@ -2662,6 +2670,12 @@ nsDOMClassInfo::Init()
DOM_CLASSINFO_UI_EVENT_MAP_ENTRIES
DOM_CLASSINFO_MAP_END
#ifdef MOZ_GAMEPAD
DOM_CLASSINFO_MAP_BEGIN(Gamepad, nsIDOMGamepad)
DOM_CLASSINFO_MAP_ENTRY(nsIDOMGamepad)
DOM_CLASSINFO_MAP_END
#endif
DOM_CLASSINFO_MAP_BEGIN(MozCSSKeyframeRule, nsIDOMMozCSSKeyframeRule)
DOM_CLASSINFO_MAP_ENTRY(nsIDOMMozCSSKeyframeRule)
DOM_CLASSINFO_MAP_END

View File

@ -263,6 +263,10 @@ DOMCI_CLASS(Touch)
DOMCI_CLASS(TouchList)
DOMCI_CLASS(TouchEvent)
#ifdef MOZ_GAMEPAD
DOMCI_CLASS(Gamepad)
#endif
DOMCI_CLASS(MozCSSKeyframeRule)
DOMCI_CLASS(MozCSSKeyframesRule)

View File

@ -211,6 +211,10 @@
#include "mozilla/dom/StructuredCloneTags.h"
#ifdef MOZ_GAMEPAD
#include "mozilla/dom/GamepadService.h"
#endif
#include "nsRefreshDriver.h"
#include "mozAutoDocUpdate.h"
@ -921,6 +925,10 @@ nsGlobalWindow::nsGlobalWindow(nsGlobalWindow *aOuterWindow)
#endif
mShowFocusRingForContent(false),
mFocusByKeyOccurred(false),
mHasGamepad(false),
#ifdef MOZ_GAMEPAD
mHasSeenGamepadInput(false),
#endif
mNotifiedIDDestroyed(false),
mAllowScriptsToClose(false),
mTimeoutInsertionPoint(nullptr),
@ -941,6 +949,10 @@ nsGlobalWindow::nsGlobalWindow(nsGlobalWindow *aOuterWindow)
{
nsLayoutStatics::AddRef();
#ifdef MOZ_GAMEPAD
mGamepads.Init();
#endif
// Initialize the PRCList (this).
PR_INIT_CLIST(this);
@ -1288,6 +1300,9 @@ nsGlobalWindow::CleanUp(bool aIgnoreModalDialog)
inner->CleanUp(aIgnoreModalDialog);
}
DisableGamepadUpdates();
mHasGamepad = false;
if (mCleanMessageManager) {
NS_ABORT_IF_FALSE(mIsChrome, "only chrome should have msg manager cleaned");
nsGlobalChromeWindow *asChrome = static_cast<nsGlobalChromeWindow*>(this);
@ -8020,6 +8035,14 @@ void nsGlobalWindow::SetIsBackground(bool aIsBackground)
if (resetTimers) {
ResetTimersForNonBackgroundWindow();
}
#ifdef MOZ_GAMEPAD
if (!aIsBackground) {
nsGlobalWindow* inner = GetCurrentInnerWindowInternal();
if (inner) {
inner->SyncGamepadState();
}
}
#endif
}
void nsGlobalWindow::MaybeUpdateTouchState()
@ -8063,6 +8086,34 @@ void nsGlobalWindow::UpdateTouchState()
}
}
void
nsGlobalWindow::EnableGamepadUpdates()
{
FORWARD_TO_INNER_VOID(EnableGamepadUpdates, ());
if (mHasGamepad) {
#ifdef MOZ_GAMEPAD
nsRefPtr<GamepadService> gamepadsvc(GamepadService::GetService());
if (gamepadsvc) {
gamepadsvc->AddListener(this);
}
#endif
}
}
void
nsGlobalWindow::DisableGamepadUpdates()
{
FORWARD_TO_INNER_VOID(DisableGamepadUpdates, ());
if (mHasGamepad) {
#ifdef MOZ_GAMEPAD
nsRefPtr<GamepadService> gamepadsvc(GamepadService::GetService());
if (gamepadsvc) {
gamepadsvc->RemoveListener(this);
}
#endif
}
}
void
nsGlobalWindow::SetChromeEventHandler(nsIDOMEventTarget* aChromeEventHandler)
{
@ -10764,6 +10815,7 @@ nsGlobalWindow::SuspendTimeouts(uint32_t aIncrease,
for (uint32_t i = 0; i < mEnabledSensors.Length(); i++)
ac->RemoveWindowListener(mEnabledSensors[i], this);
}
DisableGamepadUpdates();
// Suspend all of the workers for this window.
nsIScriptContext *scx = GetContextInternal();
@ -10844,6 +10896,7 @@ nsGlobalWindow::ResumeTimeouts(bool aThawChildren)
for (uint32_t i = 0; i < mEnabledSensors.Length(); i++)
ac->AddWindowListener(mEnabledSensors[i], this);
}
EnableGamepadUpdates();
// Resume all of the workers for this window.
nsIScriptContext *scx = GetContextInternal();
@ -10994,6 +11047,16 @@ nsGlobalWindow::DisableDeviceSensor(uint32_t aType)
}
}
void
nsGlobalWindow::SetHasGamepadEventListener(bool aHasGamepad/* = true*/)
{
FORWARD_TO_INNER_VOID(SetHasGamepadEventListener, (aHasGamepad));
mHasGamepad = aHasGamepad;
if (aHasGamepad) {
EnableGamepadUpdates();
}
}
void
nsGlobalWindow::EnableTimeChangeNotifications()
{
@ -11058,6 +11121,66 @@ nsGlobalWindow::SizeOfIncludingThis(nsWindowSizes* aWindowSizes) const
aWindowSizes->mMallocSizeOf);
}
#ifdef MOZ_GAMEPAD
void
nsGlobalWindow::AddGamepad(PRUint32 aIndex, nsDOMGamepad* aGamepad)
{
FORWARD_TO_INNER_VOID(AddGamepad, (aIndex, aGamepad));
mGamepads.Put(aIndex, aGamepad);
}
void
nsGlobalWindow::RemoveGamepad(PRUint32 aIndex)
{
FORWARD_TO_INNER_VOID(RemoveGamepad, (aIndex));
mGamepads.Remove(aIndex);
}
already_AddRefed<nsDOMGamepad>
nsGlobalWindow::GetGamepad(PRUint32 aIndex)
{
FORWARD_TO_INNER(GetGamepad, (aIndex), nullptr);
nsRefPtr<nsDOMGamepad> gamepad;
if (mGamepads.Get(aIndex, getter_AddRefs(gamepad))) {
return gamepad.forget();
}
return nullptr;
}
void
nsGlobalWindow::SetHasSeenGamepadInput(bool aHasSeen)
{
FORWARD_TO_INNER_VOID(SetHasSeenGamepadInput, (aHasSeen));
mHasSeenGamepadInput = aHasSeen;
}
bool
nsGlobalWindow::HasSeenGamepadInput()
{
FORWARD_TO_INNER(HasSeenGamepadInput, (), false);
return mHasSeenGamepadInput;
}
// static
PLDHashOperator
nsGlobalWindow::EnumGamepadsForSync(const PRUint32& aKey, nsDOMGamepad* aData, void* userArg)
{
nsRefPtr<GamepadService> gamepadsvc(GamepadService::GetService());
gamepadsvc->SyncGamepadState(aKey, aData);
return PL_DHASH_NEXT;
}
void
nsGlobalWindow::SyncGamepadState()
{
FORWARD_TO_INNER_VOID(SyncGamepadState, ());
if (mHasSeenGamepadInput) {
mGamepads.EnumerateRead(EnumGamepadsForSync, NULL);
}
}
#endif
// nsGlobalChromeWindow implementation
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(nsGlobalChromeWindow,

View File

@ -62,6 +62,9 @@
#include "nsWrapperCacheInlines.h"
#include "nsIIdleObserver.h"
#include "nsIDOMWakeLock.h"
#ifdef MOZ_GAMEPAD
#include "nsDOMGamepad.h"
#endif
#include "mozilla/dom/EventTarget.h"
@ -378,6 +381,8 @@ public:
virtual NS_HIDDEN_(void) RefreshCompartmentPrincipal();
virtual NS_HIDDEN_(nsresult) SetFullScreenInternal(bool aIsFullScreen, bool aRequireTrust);
virtual NS_HIDDEN_(void) SetHasGamepadEventListener(bool aHasGamepad = true);
// nsIDOMStorageIndexedDB
NS_DECL_NSIDOMSTORAGEINDEXEDDB
@ -609,6 +614,23 @@ public:
mAllowScriptsToClose = true;
}
#ifdef MOZ_GAMEPAD
void AddGamepad(PRUint32 aIndex, nsDOMGamepad* aGamepad);
void RemoveGamepad(PRUint32 aIndex);
already_AddRefed<nsDOMGamepad> GetGamepad(PRUint32 aIndex);
void SetHasSeenGamepadInput(bool aHasSeen);
bool HasSeenGamepadInput();
void SyncGamepadState();
static PLDHashOperator EnumGamepadsForSync(const PRUint32& aKey,
nsDOMGamepad* aData,
void* userArg);
#endif
// Enable/disable updates for gamepad input.
void EnableGamepadUpdates();
void DisableGamepadUpdates();
#define EVENT(name_, id_, type_, struct_) \
mozilla::dom::EventHandlerNonNull* GetOn##name_() \
{ \
@ -1044,6 +1066,13 @@ protected:
// should be displayed.
bool mFocusByKeyOccurred : 1;
// Indicates whether this window wants gamepad input events
bool mHasGamepad : 1;
#ifdef MOZ_GAMEPAD
nsRefPtrHashtable<nsUint32HashKey, nsDOMGamepad> mGamepads;
bool mHasSeenGamepadInput;
#endif
// whether we've sent the destroy notification for our window id
bool mNotifiedIDDestroyed : 1;
// whether scripts may close the window,

View File

@ -57,8 +57,8 @@ class AudioContext;
}
#define NS_PIDOMWINDOW_IID \
{ 0xf5af1c3c, 0xebad, 0x4d00, \
{ 0xa2, 0xa4, 0x12, 0x2e, 0x27, 0x16, 0x59, 0x01 } }
{0xf30405c2, 0x5da8, 0x4339, \
{0x87, 0xe2, 0xfa, 0xb2, 0x51, 0x26, 0x8a, 0xe8}}
class nsPIDOMWindow : public nsIDOMWindowInternal
{
@ -599,6 +599,11 @@ public:
virtual void DisableNetworkEvent(uint32_t aType) = 0;
#endif // MOZ_B2G
/**
* Tell this window that there is an observer for gamepad input
*/
virtual void SetHasGamepadEventListener(bool aHasGamepad = true) = 0;
/**
* Set a arguments for this window. This will be set on the window
* right away (if there's an existing document) and it will also be

View File

@ -47,6 +47,9 @@ XPIDL_SOURCES += [
'nsIDOMUIEvent.idl',
'nsIDOMUserProximityEvent.idl',
'nsIDOMWheelEvent.idl',
'nsIDOMGamepadButtonEvent.idl',
'nsIDOMGamepadAxisMoveEvent.idl',
'nsIDOMGamepadEvent.idl',
]
XPIDL_MODULE = 'dom_events'

View File

@ -0,0 +1,33 @@
/* 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 "nsIDOMGamepadEvent.idl"
[builtinclass, scriptable, uuid(bd09eef8-8e07-4baf-8d39-4f92003dbca8)]
interface nsIDOMGamepadAxisMoveEvent : nsIDOMGamepadEvent
{
/**
* Index in gamepad.axes of the axis that was moved.
*/
readonly attribute unsigned long axis;
/**
* Position of the axis in the range -1.0...1.0.
*/
readonly attribute double value;
[noscript]
void initGamepadAxisMoveEvent(in DOMString typeArg,
in boolean canBubbleArg,
in boolean cancelableArg,
in nsIDOMGamepad gamepad,
in unsigned long axis,
in double value);
};
dictionary GamepadAxisMoveEventInit : GamepadEventInit
{
unsigned long axis;
double value;
};

View File

@ -0,0 +1,26 @@
/* 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 "nsIDOMGamepadEvent.idl"
[builtinclass, scriptable, uuid(d75d4d2b-e7b4-4b93-ac35-2e70b57d9b28)]
interface nsIDOMGamepadButtonEvent : nsIDOMGamepadEvent
{
/**
* Index in gamepad.buttons of the button that was pressed or released.
*/
readonly attribute unsigned long button;
[noscript]
void initGamepadButtonEvent(in DOMString typeArg,
in boolean canBubbleArg,
in boolean cancelableArg,
in nsIDOMGamepad gamepad,
in unsigned long button);
};
dictionary GamepadButtonEventInit : GamepadEventInit
{
unsigned long button;
};

View File

@ -0,0 +1,27 @@
/* 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 "nsIDOMEvent.idl"
interface nsIDOMGamepad;
[builtinclass, scriptable, uuid(93b048d6-2aef-46a9-b0f4-06d7f00d8ef2)]
interface nsIDOMGamepadEvent : nsIDOMEvent
{
/**
* The device that generated this event.
*/
readonly attribute nsIDOMGamepad gamepad;
[noscript]
void initGamepadEvent(in DOMString typeArg,
in boolean canBubbleArg,
in boolean cancelableArg,
in nsIDOMGamepad gamepad);
};
dictionary GamepadEventInit : EventInit
{
nsIDOMGamepad gamepad;
};

View File

@ -0,0 +1,12 @@
# 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/.
DEPTH = ../../..
topsrcdir = @top_srcdir@
srcdir = @srcdir@
VPATH = @srcdir@
include $(DEPTH)/config/autoconf.mk
include $(topsrcdir)/config/rules.mk

View File

@ -0,0 +1,11 @@
# vim: set filetype=python:
# 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/.
XPIDL_MODULE = 'dom_gamepad'
XPIDL_SOURCES = [
'nsIDOMGamepad.idl',
'nsIGamepadServiceTest.idl',
]

View File

@ -0,0 +1,39 @@
/* 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 "nsISupports.idl"
interface nsIVariant;
[builtinclass, scriptable, uuid(ff13acd9-11da-4817-8f2a-4a5700dfd13e)]
interface nsIDOMGamepad : nsISupports
{
/**
* An identifier, unique per type of device.
*/
readonly attribute DOMString id;
/**
* The game port index for the device. Unique per device
* attached to this system.
*/
readonly attribute unsigned long index;
/**
* true if this gamepad is currently connected to the system.
*/
readonly attribute boolean connected;
/**
* The current state of all buttons on the device, an
* array of doubles.
*/
readonly attribute nsIVariant buttons;
/**
* The current position of all axes on the device, an
* array of doubles.
*/
readonly attribute nsIVariant axes;
};

View File

@ -0,0 +1,22 @@
/* 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 "nsISupports.idl"
interface nsIVariant;
/*
* This interface is intended only for use in tests.
*/
[scriptable, uuid(7edf77a2-6b3e-4bbb-9100-4452d425feaa)]
interface nsIGamepadServiceTest : nsISupports
{
unsigned long addGamepad(in string id, in unsigned long numButtons,
in unsigned long numAxes);
void removeGamepad(in unsigned long index);
void newButtonEvent(in unsigned long index, in unsigned long button,
in boolean pressed);
void newAxisMoveEvent(in unsigned long index, in unsigned long axis,
in double value);
};

View File

@ -29,6 +29,7 @@ interfaces = [
'svg',
'smil',
'apps',
'gamepad',
]
PARALLEL_DIRS += ['interfaces/' + i for i in interfaces]

View File

@ -0,0 +1,530 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "mozilla/Hal.h"
#include "mozilla/ClearOnShutdown.h"
#include "mozilla/StaticPtr.h"
#include "GamepadService.h"
#include "nsAutoPtr.h"
#include "nsIDOMEvent.h"
#include "nsIDOMDocument.h"
#include "nsIDOMEventTarget.h"
#include "nsDOMGamepad.h"
#include "nsIDOMGamepadButtonEvent.h"
#include "nsIDOMGamepadAxisMoveEvent.h"
#include "nsIDOMGamepadEvent.h"
#include "GeneratedEvents.h"
#include "nsIDOMWindow.h"
#include "nsIObserver.h"
#include "nsIObserverService.h"
#include "nsIServiceManager.h"
#include "nsITimer.h"
#include "nsThreadUtils.h"
#include "mozilla/Services.h"
#include <cstddef>
namespace mozilla {
namespace dom {
namespace {
// Amount of time to wait before cleaning up gamepad resources
// when no pages are listening for events.
const int kCleanupDelayMS = 2000;
const nsTArray<nsRefPtr<nsGlobalWindow> >::index_type NoIndex =
nsTArray<nsRefPtr<nsGlobalWindow> >::NoIndex;
StaticRefPtr<GamepadService> gGamepadServiceSingleton;
} // namespace
bool GamepadService::sShutdown = false;
NS_IMPL_ISUPPORTS0(GamepadService)
GamepadService::GamepadService()
: mStarted(false),
mShuttingDown(false)
{
nsCOMPtr<nsIObserverService> observerService =
mozilla::services::GetObserverService();
observerService->AddObserver(this,
NS_XPCOM_WILL_SHUTDOWN_OBSERVER_ID,
false);
}
NS_IMETHODIMP
GamepadService::Observe(nsISupports* aSubject,
const char* aTopic,
const PRUnichar* aData)
{
nsCOMPtr<nsIObserverService> observerService =
mozilla::services::GetObserverService();
nsCOMPtr<nsIObserver> observer = do_QueryInterface(this);
observerService->RemoveObserver(observer, NS_XPCOM_WILL_SHUTDOWN_OBSERVER_ID);
BeginShutdown();
return NS_OK;
}
void
GamepadService::BeginShutdown()
{
mShuttingDown = true;
if (mTimer) {
mTimer->Cancel();
}
mozilla::hal::StopMonitoringGamepadStatus();
mStarted = false;
// Don't let windows call back to unregister during shutdown
for (uint32_t i = 0; i < mListeners.Length(); i++) {
mListeners[i]->SetHasGamepadEventListener(false);
}
mListeners.Clear();
mGamepads.Clear();
sShutdown = true;
}
void
GamepadService::AddListener(nsGlobalWindow* aWindow)
{
if (mShuttingDown) {
return;
}
if (mListeners.IndexOf(aWindow) != NoIndex) {
return; // already exists
}
if (!mStarted) {
mozilla::hal::StartMonitoringGamepadStatus();
mStarted = true;
}
mListeners.AppendElement(aWindow);
}
void
GamepadService::RemoveListener(nsGlobalWindow* aWindow)
{
if (mShuttingDown) {
// Doesn't matter at this point. It's possible we're being called
// as a result of our own destructor here, so just bail out.
return;
}
if (mListeners.IndexOf(aWindow) == NoIndex) {
return; // doesn't exist
}
mListeners.RemoveElement(aWindow);
if (mListeners.Length() == 0 && !mShuttingDown) {
StartCleanupTimer();
}
}
uint32_t
GamepadService::AddGamepad(const char* aId,
uint32_t aNumButtons,
uint32_t aNumAxes)
{
//TODO: bug 852258: get initial button/axis state
nsRefPtr<nsDOMGamepad> gamepad =
new nsDOMGamepad(NS_ConvertUTF8toUTF16(nsDependentCString(aId)),
0,
aNumButtons,
aNumAxes);
int index = -1;
for (uint32_t i = 0; i < mGamepads.Length(); i++) {
if (!mGamepads[i]) {
mGamepads[i] = gamepad;
index = i;
break;
}
}
if (index == -1) {
mGamepads.AppendElement(gamepad);
index = mGamepads.Length() - 1;
}
gamepad->SetIndex(index);
NewConnectionEvent(index, true);
return index;
}
void
GamepadService::RemoveGamepad(uint32_t aIndex)
{
if (aIndex < mGamepads.Length()) {
mGamepads[aIndex]->SetConnected(false);
NewConnectionEvent(aIndex, false);
// If this is the last entry in the list, just remove it.
if (aIndex == mGamepads.Length() - 1) {
mGamepads.RemoveElementAt(aIndex);
} else {
// Otherwise just null it out and leave it, so the
// indices of the following entries remain valid.
mGamepads[aIndex] = nullptr;
}
}
}
void
GamepadService::NewButtonEvent(uint32_t aIndex, uint32_t aButton, bool aPressed)
{
if (mShuttingDown || aIndex >= mGamepads.Length()) {
return;
}
double value = aPressed ? 1.0L : 0.0L;
mGamepads[aIndex]->SetButton(aButton, value);
// Hold on to listeners in a separate array because firing events
// can mutate the mListeners array.
nsTArray<nsRefPtr<nsGlobalWindow> > listeners(mListeners);
for (uint32_t i = listeners.Length(); i > 0 ; ) {
--i;
// Only send events to non-background windows
if (!listeners[i]->GetOuterWindow() ||
listeners[i]->GetOuterWindow()->IsBackground()) {
continue;
}
if (!WindowHasSeenGamepad(listeners[i], aIndex)) {
SetWindowHasSeenGamepad(listeners[i], aIndex);
// This window hasn't seen this gamepad before, so
// send a connection event first.
NewConnectionEvent(aIndex, true);
}
nsRefPtr<nsDOMGamepad> gamepad = listeners[i]->GetGamepad(aIndex);
if (gamepad) {
gamepad->SetButton(aButton, value);
// Fire event
FireButtonEvent(listeners[i], gamepad, aButton, value);
}
}
}
void
GamepadService::FireButtonEvent(EventTarget* aTarget,
nsDOMGamepad* aGamepad,
uint32_t aButton,
double aValue)
{
nsCOMPtr<nsIDOMEvent> event;
bool defaultActionEnabled = true;
NS_NewDOMGamepadButtonEvent(getter_AddRefs(event), aTarget, nullptr, nullptr);
nsCOMPtr<nsIDOMGamepadButtonEvent> je = do_QueryInterface(event);
MOZ_ASSERT(je, "QI should not fail");
nsString name = aValue == 1.0L ? NS_LITERAL_STRING("gamepadbuttondown") :
NS_LITERAL_STRING("gamepadbuttonup");
je->InitGamepadButtonEvent(name, false, false, aGamepad, aButton);
je->SetTrusted(true);
aTarget->DispatchEvent(event, &defaultActionEnabled);
}
void
GamepadService::NewAxisMoveEvent(uint32_t aIndex, uint32_t aAxis, double aValue)
{
if (mShuttingDown || aIndex >= mGamepads.Length()) {
return;
}
mGamepads[aIndex]->SetAxis(aAxis, aValue);
// Hold on to listeners in a separate array because firing events
// can mutate the mListeners array.
nsTArray<nsRefPtr<nsGlobalWindow> > listeners(mListeners);
for (uint32_t i = listeners.Length(); i > 0 ; ) {
--i;
// Only send events to non-background windows
if (!listeners[i]->GetOuterWindow() ||
listeners[i]->GetOuterWindow()->IsBackground()) {
continue;
}
if (!WindowHasSeenGamepad(listeners[i], aIndex)) {
SetWindowHasSeenGamepad(listeners[i], aIndex);
// This window hasn't seen this gamepad before, so
// send a connection event first.
NewConnectionEvent(aIndex, true);
}
nsRefPtr<nsDOMGamepad> gamepad = listeners[i]->GetGamepad(aIndex);
if (gamepad) {
gamepad->SetAxis(aAxis, aValue);
// Fire event
FireAxisMoveEvent(listeners[i], gamepad, aAxis, aValue);
}
}
}
void
GamepadService::FireAxisMoveEvent(EventTarget* aTarget,
nsDOMGamepad* aGamepad,
uint32_t aAxis,
double aValue)
{
nsCOMPtr<nsIDOMEvent> event;
bool defaultActionEnabled = true;
NS_NewDOMGamepadAxisMoveEvent(getter_AddRefs(event), aTarget, nullptr,
nullptr);
nsCOMPtr<nsIDOMGamepadAxisMoveEvent> je = do_QueryInterface(event);
MOZ_ASSERT(je, "QI should not fail");
je->InitGamepadAxisMoveEvent(NS_LITERAL_STRING("gamepadaxismove"),
false, false, aGamepad, aAxis, aValue);
je->SetTrusted(true);
aTarget->DispatchEvent(event, &defaultActionEnabled);
}
void
GamepadService::NewConnectionEvent(uint32_t aIndex, bool aConnected)
{
if (mShuttingDown || aIndex >= mGamepads.Length()) {
return;
}
// Hold on to listeners in a separate array because firing events
// can mutate the mListeners array.
nsTArray<nsRefPtr<nsGlobalWindow> > listeners(mListeners);
if (aConnected) {
for (uint32_t i = listeners.Length(); i > 0 ; ) {
--i;
// Only send events to non-background windows
if (!listeners[i]->GetOuterWindow() ||
listeners[i]->GetOuterWindow()->IsBackground())
continue;
// We don't fire a connected event here unless the window
// has seen input from at least one device.
if (aConnected && !listeners[i]->HasSeenGamepadInput()) {
return;
}
SetWindowHasSeenGamepad(listeners[i], aIndex);
nsRefPtr<nsDOMGamepad> gamepad = listeners[i]->GetGamepad(aIndex);
if (gamepad) {
// Fire event
FireConnectionEvent(listeners[i], gamepad, aConnected);
}
}
} else {
// For disconnection events, fire one at every window that has received
// data from this gamepad.
for (uint32_t i = listeners.Length(); i > 0 ; ) {
--i;
// Even background windows get these events, so we don't have to
// deal with the hassle of syncing the state of removed gamepads.
if (WindowHasSeenGamepad(listeners[i], aIndex)) {
nsRefPtr<nsDOMGamepad> gamepad = listeners[i]->GetGamepad(aIndex);
if (gamepad) {
gamepad->SetConnected(false);
// Fire event
FireConnectionEvent(listeners[i], gamepad, false);
}
if (gamepad) {
listeners[i]->RemoveGamepad(aIndex);
}
}
}
}
}
void
GamepadService::FireConnectionEvent(EventTarget* aTarget,
nsDOMGamepad* aGamepad,
bool aConnected)
{
nsCOMPtr<nsIDOMEvent> event;
bool defaultActionEnabled = true;
NS_NewDOMGamepadEvent(getter_AddRefs(event), aTarget, nullptr, nullptr);
nsCOMPtr<nsIDOMGamepadEvent> je = do_QueryInterface(event);
MOZ_ASSERT(je, "QI should not fail");
nsString name = aConnected ? NS_LITERAL_STRING("gamepadconnected") :
NS_LITERAL_STRING("gamepaddisconnected");
je->InitGamepadEvent(name, false, false, aGamepad);
je->SetTrusted(true);
aTarget->DispatchEvent(event, &defaultActionEnabled);
}
void
GamepadService::SyncGamepadState(uint32_t aIndex, nsDOMGamepad* aGamepad)
{
if (mShuttingDown || aIndex > mGamepads.Length()) {
return;
}
aGamepad->SyncState(mGamepads[aIndex]);
}
// static
already_AddRefed<GamepadService>
GamepadService::GetService()
{
if (sShutdown) {
return nullptr;
}
if (!gGamepadServiceSingleton) {
gGamepadServiceSingleton = new GamepadService();
ClearOnShutdown(&gGamepadServiceSingleton);
}
nsRefPtr<GamepadService> service(gGamepadServiceSingleton);
return service.forget();
}
bool
GamepadService::WindowHasSeenGamepad(nsGlobalWindow* aWindow, uint32_t aIndex)
{
nsRefPtr<nsDOMGamepad> gamepad = aWindow->GetGamepad(aIndex);
return gamepad != nullptr;
}
void
GamepadService::SetWindowHasSeenGamepad(nsGlobalWindow* aWindow,
uint32_t aIndex,
bool aHasSeen)
{
if (mListeners.IndexOf(aWindow) == NoIndex) {
// This window isn't even listening for gamepad events.
return;
}
if (aHasSeen) {
aWindow->SetHasSeenGamepadInput(true);
nsRefPtr<nsDOMGamepad> gamepad = mGamepads[aIndex]->Clone();
aWindow->AddGamepad(aIndex, gamepad);
} else {
aWindow->RemoveGamepad(aIndex);
}
}
// static
void
GamepadService::TimeoutHandler(nsITimer* aTimer, void* aClosure)
{
// the reason that we use self, instead of just using nsITimerCallback or nsIObserver
// is so that subclasses are free to use timers without worry about the base classes's
// usage.
GamepadService* self = reinterpret_cast<GamepadService*>(aClosure);
if (!self) {
NS_ERROR("no self");
return;
}
if (self->mShuttingDown) {
return;
}
if (self->mListeners.Length() == 0) {
mozilla::hal::StopMonitoringGamepadStatus();
self->mStarted = false;
if (!self->mGamepads.IsEmpty()) {
self->mGamepads.Clear();
}
}
}
void
GamepadService::StartCleanupTimer()
{
if (mTimer) {
mTimer->Cancel();
}
mTimer = do_CreateInstance("@mozilla.org/timer;1");
if (mTimer) {
mTimer->InitWithFuncCallback(TimeoutHandler,
this,
kCleanupDelayMS,
nsITimer::TYPE_ONE_SHOT);
}
}
/*
* Implementation of the test service. This is just to provide a simple binding
* of the GamepadService to JavaScript via XPCOM so that we can write Mochitests
* that add and remove fake gamepads, avoiding the platform-specific backends.
*/
NS_IMPL_ISUPPORTS1(GamepadServiceTest, nsIGamepadServiceTest)
GamepadServiceTest* GamepadServiceTest::sSingleton = nullptr;
// static
already_AddRefed<GamepadServiceTest>
GamepadServiceTest::CreateService()
{
if (sSingleton == nullptr) {
sSingleton = new GamepadServiceTest();
}
nsRefPtr<GamepadServiceTest> service = sSingleton;
return service.forget();
}
GamepadServiceTest::GamepadServiceTest()
{
/* member initializers and constructor code */
}
/* uint32_t addGamepad (in string id, in uint32_t numButtons, in uint32_t numAxes); */
NS_IMETHODIMP GamepadServiceTest::AddGamepad(const char* aID,
uint32_t aNumButtons,
uint32_t aNumAxes,
uint32_t* aRetval)
{
*aRetval = gGamepadServiceSingleton->AddGamepad(aID,
aNumButtons,
aNumAxes);
return NS_OK;
}
/* void removeGamepad (in uint32_t index); */
NS_IMETHODIMP GamepadServiceTest::RemoveGamepad(uint32_t aIndex)
{
gGamepadServiceSingleton->RemoveGamepad(aIndex);
return NS_OK;
}
/* void newButtonEvent (in uint32_t index, in uint32_t button,
in boolean pressed); */
NS_IMETHODIMP GamepadServiceTest::NewButtonEvent(uint32_t aIndex,
uint32_t aButton,
bool aPressed)
{
gGamepadServiceSingleton->NewButtonEvent(aIndex, aButton, aPressed);
return NS_OK;
}
/* void newAxisMoveEvent (in uint32_t index, in uint32_t axis,
in double value); */
NS_IMETHODIMP GamepadServiceTest::NewAxisMoveEvent(uint32_t aIndex,
uint32_t aAxis,
double aValue)
{
gGamepadServiceSingleton->NewAxisMoveEvent(aIndex, aAxis, aValue);
return NS_OK;
}
} // namespace dom
} // namespace mozilla

126
dom/system/GamepadService.h Normal file
View File

@ -0,0 +1,126 @@
/* 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 mozilla_dom_GamepadService_h_
#define mozilla_dom_GamepadService_h_
#include "mozilla/StandardInteger.h"
#include "nsAutoPtr.h"
#include "nsCOMArray.h"
#include "nsDOMGamepad.h"
#include "nsIGamepadServiceTest.h"
#include "nsGlobalWindow.h"
#include "nsIFocusManager.h"
#include "nsIObserver.h"
#include "nsITimer.h"
#include "nsTArray.h"
class nsIDOMDocument;
namespace mozilla {
namespace dom {
class EventTarget;
class GamepadService : public nsIObserver
{
public:
NS_DECL_ISUPPORTS
NS_DECL_NSIOBSERVER
// Get the singleton service
static already_AddRefed<GamepadService> GetService();
void BeginShutdown();
// Indicate that |aWindow| wants to receive gamepad events.
void AddListener(nsGlobalWindow* aWindow);
// Indicate that |aWindow| should no longer receive gamepad events.
void RemoveListener(nsGlobalWindow* aWindow);
// Add a gamepad to the list of known gamepads, and return its index.
uint32_t AddGamepad(const char* aID, uint32_t aNumButtons, uint32_t aNumAxes);
// Remove the gamepad at |aIndex| from the list of known gamepads.
void RemoveGamepad(uint32_t aIndex);
//TODO: the spec uses double values for buttons, to allow for analog
// buttons.
void NewButtonEvent(uint32_t aIndex, uint32_t aButton, bool aPressed);
void NewAxisMoveEvent(uint32_t aIndex, uint32_t aAxis, double aValue);
// Synchronize the state of |aGamepad| to match the gamepad stored at |aIndex|
void SyncGamepadState(uint32_t aIndex, nsDOMGamepad* aGamepad);
protected:
GamepadService();
virtual ~GamepadService() {};
void StartCleanupTimer();
void NewConnectionEvent(uint32_t aIndex, bool aConnected);
void FireAxisMoveEvent(EventTarget* aTarget,
nsDOMGamepad* aGamepad,
uint32_t axis,
double value);
void FireButtonEvent(EventTarget* aTarget,
nsDOMGamepad* aGamepad,
uint32_t aButton,
double aValue);
void FireConnectionEvent(EventTarget* aTarget,
nsDOMGamepad* aGamepad,
bool aConnected);
// true if the platform-specific backend has started work
bool mStarted;
// true when shutdown has begun
bool mShuttingDown;
private:
// Returns true if we have already sent data from this gamepad
// to this window. This should only return true if the user
// explicitly interacted with a gamepad while this window
// was focused, by pressing buttons or similar actions.
bool WindowHasSeenGamepad(nsGlobalWindow* aWindow, uint32_t aIndex);
// Indicate that a window has recieved data from a gamepad.
void SetWindowHasSeenGamepad(nsGlobalWindow* aWindow, uint32_t aIndex,
bool aHasSeen = true);
static void TimeoutHandler(nsITimer* aTimer, void* aClosure);
static bool sShutdown;
// Gamepads connected to the system. Copies of these are handed out
// to each window.
nsTArray<nsRefPtr<nsDOMGamepad> > mGamepads;
// nsGlobalWindows that are listening for gamepad events.
// has been sent to that window.
nsTArray<nsRefPtr<nsGlobalWindow> > mListeners;
nsCOMPtr<nsITimer> mTimer;
nsCOMPtr<nsIFocusManager> mFocusManager;
nsCOMPtr<nsIObserver> mObserver;
};
// Service for testing purposes
class GamepadServiceTest : public nsIGamepadServiceTest
{
public:
NS_DECL_ISUPPORTS
NS_DECL_NSIGAMEPADSERVICETEST
GamepadServiceTest();
static already_AddRefed<GamepadServiceTest> CreateService();
private:
static GamepadServiceTest* sSingleton;
virtual ~GamepadServiceTest() {};
};
} // namespace dom
} // namespace mozilla
#define NS_GAMEPAD_TEST_CID \
{ 0xfb1fcb57, 0xebab, 0x4cf4, \
{ 0x96, 0x3b, 0x1e, 0x4d, 0xb8, 0x52, 0x16, 0x96 } }
#define NS_GAMEPAD_TEST_CONTRACTID "@mozilla.org/gamepad-test;1"
#endif // mozilla_dom_GamepadService_h_

View File

@ -20,6 +20,10 @@ CPPSRCS = \
OSFileConstants.cpp \
$(NULL)
ifdef MOZ_GAMEPAD
CPPSRCS += GamepadService.cpp
endif
# We fire the nsDOMDeviceAcceleration
LOCAL_INCLUDES += \
-I$(topsrcdir)/content/events/src \
@ -55,11 +59,16 @@ EXPORTS_mozilla = \
OSFileConstants.h \
$(NULL)
# We fire the nsDOMDeviceAcceleration
LOCAL_INCLUDES += \
-I$(topsrcdir)/content/events/src \
-I$(topsrcdir)/js/xpconnect/loader \
$(NULL)
ifdef MOZ_GAMEPAD
EXPORTS_NAMESPACES += mozilla/dom
EXPORTS_mozilla/dom = \
GamepadService.h \
$(NULL)
endif
include $(topsrcdir)/config/config.mk
@ -69,4 +78,3 @@ FORCE_STATIC_LIB = 1
EXPORT_LIBRARY = 1
include $(topsrcdir)/ipc/chromium/chromium-config.mk
include $(topsrcdir)/config/rules.mk

View File

@ -0,0 +1,23 @@
# 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/.
DEPTH = ../../../..
topsrcdir = @top_srcdir@
srcdir = @srcdir@
VPATH = @srcdir@
relativesrcdir = dom/tests/mochitest/gamepad
include $(DEPTH)/config/autoconf.mk
MOCHITEST_FILES = \
test_gamepad_basic.html \
test_gamepad.html \
test_gamepad_frame_state_sync.html \
gamepad_frame_state.html \
test_gamepad_hidden_frame.html \
gamepad_frame.html \
mock_gamepad.js \
$(NULL)
include $(topsrcdir)/config/rules.mk

View File

@ -0,0 +1,16 @@
<!-- Any copyright is dedicated to the Public Domain.
- http://creativecommons.org/publicdomain/zero/1.0/ -->
<!DOCTYPE HTML>
<html>
<head>
<title>frame</title>
<script type="text/javascript">
var buttonPresses = 0;
window.addEventListener("gamepadbuttondown", function() {
buttonPresses++;
});
</script>
</head>
<body>
</body>
</html>

View File

@ -0,0 +1,16 @@
<!-- Any copyright is dedicated to the Public Domain.
- http://creativecommons.org/publicdomain/zero/1.0/ -->
<!DOCTYPE HTML>
<html>
<head>
<title>frame</title>
<script type="text/javascript">
var gamepad;
window.addEventListener("gamepadconnected", function(e) {
gamepad = e.gamepad;
});
</script>
</head>
<body>
</body>
</html>

View File

@ -0,0 +1,5 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/ */
var GamepadService = (function() {
return SpecialPowers.Cc["@mozilla.org/gamepad-test;1"].getService(SpecialPowers.Ci.nsIGamepadServiceTest);
})();

View File

@ -0,0 +1,4 @@
# vim: set filetype=python:
# 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/.

View File

@ -0,0 +1,33 @@
<!-- Any copyright is dedicated to the Public Domain.
- http://creativecommons.org/publicdomain/zero/1.0/ -->
<!DOCTYPE HTML>
<html>
<head>
<title>Test gamepad</title>
<script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
</head>
<body>
<script type="text/javascript" src="mock_gamepad.js"></script>
<script class="testbody" type="text/javascript">
SimpleTest.waitForExplicitFinish();
window.addEventListener("gamepadconnected", connecthandler);
// Add a gamepad
var index = GamepadService.addGamepad("test gamepad", // id
4, // buttons
2);// axes
// Press a button
GamepadService.newButtonEvent(index, 0, true);
function connecthandler(e) {
is(e.gamepad.id, "test gamepad", "correct gamepad name");
is(e.gamepad.buttons.length, 4, "correct number of buttons");
is(e.gamepad.axes.length, 2, "correct number of axes");
SimpleTest.executeSoon(function() {
GamepadService.removeGamepad(index);
SimpleTest.finish();
});
}
</script>
</body>
</html>

View File

@ -0,0 +1,22 @@
<!-- Any copyright is dedicated to the Public Domain.
- http://creativecommons.org/publicdomain/zero/1.0/ -->
<!DOCTYPE HTML>
<html>
<head>
<title>Test gamepad </title>
<script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
</head>
<body>
<script class="testbody" type="text/javascript">
// Not much we can test here, but this is enough to get the
// gamepad service up and running, which should test that it
// doesn't leak anything.
window.addEventListener("gamepadconnected", function() {});
SimpleTest.ok(true, "dummy check");
</script>
</body>
</html>

View File

@ -0,0 +1,85 @@
<!-- Any copyright is dedicated to the Public Domain.
- http://creativecommons.org/publicdomain/zero/1.0/ -->
<!DOCTYPE HTML>
<html>
<head>
<title>Test hidden frames</title>
<script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
</head>
<body>
<script type="text/javascript" src="mock_gamepad.js"></script>
<script class="testbody" type="text/javascript">
SimpleTest.waitForExplicitFinish();
var index = GamepadService.addGamepad("test gamepad", // id
4, // buttons
2);// axes
function setFrameVisible(f, visible) {
var Ci = SpecialPowers.wrap(Components.interfaces);
var docshell = SpecialPowers.wrap(f.contentWindow).QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIWebNavigation).QueryInterface(Ci.nsIDocShell);
docshell.isActive = visible;
}
var frames_loaded = 0;
var f1, f2;
function frame_loaded() {
frames_loaded++;
if (frames_loaded == 2) {
f1 = document.getElementById('f1');
f2 = document.getElementById('f2');
// Now press the button, but don't release it.
GamepadService.newButtonEvent(index, 0, true);
}
}
window.addEventListener("gamepadbuttondown", function() {
// Wait to ensure that all frames received the button press as well.
SpecialPowers.executeSoon(tests[testNum++]);
});
var testNum = 0;
var tests = [
check_button_pressed,
check_second_frame_no_button_press,
];
function check_button_pressed() {
// At this point the both frames should see the button as pressed.
is(f1.contentWindow.gamepad.buttons[0], 1, "frame 1 sees button pressed");
is(f2.contentWindow.gamepad.buttons[0], 1, "frame 2 sees button pressed");
// Now release the button, then hide the second frame.
GamepadService.newButtonEvent(index, 0, false);
setFrameVisible(f2, false);
SpecialPowers.executeSoon(function() {
// Now press the button, but don't release it.
GamepadService.newButtonEvent(index, 0, true);
});
}
function check_second_frame_no_button_press () {
/*
* At this point the first frame should see the button as pressed,
* but the second frame should not, since it's hidden.
*/
is(f1.contentWindow.gamepad.buttons[0], 1, "frame 1 sees button pressed");
is(f2.contentWindow.gamepad.buttons[0], 0, "frame 2 should not see button pressed");
// Now unhide the second frame.
setFrameVisible(f2, true);
SpecialPowers.executeSoon(function() {
// Now that the frame is visible again, it should see the button
// that was pressed.
is(f2.contentWindow.gamepad.buttons[0], 1, "frame 2 sees button pressed");
// cleanup
GamepadService.removeGamepad(index);
SimpleTest.finish();
});
}
</script>
<iframe id="f1" src="gamepad_frame_state.html" onload="frame_loaded()"></iframe>
<iframe id="f2" src="gamepad_frame_state.html" onload="frame_loaded()"></iframe>
</body>
</html>

View File

@ -0,0 +1,71 @@
<!-- Any copyright is dedicated to the Public Domain.
- http://creativecommons.org/publicdomain/zero/1.0/ -->
<!DOCTYPE HTML>
<html>
<head>
<title>Test hidden frames</title>
<script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
</head>
<body>
<script type="text/javascript" src="mock_gamepad.js"></script>
<script class="testbody" type="text/javascript">
SimpleTest.waitForExplicitFinish();
var index = GamepadService.addGamepad("test gamepad", // id
4, // buttons
2);// axes
function pressButton() {
GamepadService.newButtonEvent(index, 0, true);
GamepadService.newButtonEvent(index, 0, false);
}
function setFrameVisible(f, visible) {
var Ci = SpecialPowers.wrap(Components.interfaces);
var docshell = SpecialPowers.wrap(f.contentWindow).QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIWebNavigation).QueryInterface(Ci.nsIDocShell);
docshell.isActive = visible;
}
var frames_loaded = 0;
var f1, f2;
function frame_loaded() {
frames_loaded++;
if (frames_loaded == 2) {
f1 = document.getElementById('f1');
f2 = document.getElementById('f2');
pressButton();
}
}
window.addEventListener("gamepadbuttondown", function() {
// Wait to ensure that all frames received the button press as well.
SpecialPowers.executeSoon(tests[testNum++]);
});
var testNum = 0;
var tests = [
test1,
test2,
];
function test1() {
is(f1.contentWindow.buttonPresses, 1, "right number of button presses in frame 1");
is(f2.contentWindow.buttonPresses, 1, "right number of button presses in frame 2");
// Now hide the second frame and send another button press.
setFrameVisible(f2, false);
SpecialPowers.executeSoon(function() { pressButton(); });
}
function test2() {
is(f1.contentWindow.buttonPresses, 2, "right number of button presses in frame 1");
is(f2.contentWindow.buttonPresses, 1, "right number of button presses in frame 2");
GamepadService.removeGamepad(index);
SimpleTest.finish();
}
</script>
<iframe id="f1" src="gamepad_frame.html" onload="frame_loaded()"></iframe>
<iframe id="f2" src="gamepad_frame.html" onload="frame_loaded()"></iframe>
</body>
</html>

View File

@ -540,7 +540,11 @@ var interfaceNamesInGlobalScope =
"AsyncScrollEventDetail",
"MozSmsSegmentInfo",
"DOMCursor",
"BlobEvent"
"BlobEvent",
"Gamepad",
"GamepadEvent",
"GamepadButtonEvent",
"GamepadAxisMoveEvent"
]
for (var i in SpecialPowers.Components.interfaces) {

View File

@ -25,6 +25,9 @@ DIRS += [
'webcomponents',
]
if CONFIG['MOZ_GAMEPAD']:
DIRS += ['gamepad']
#needs IPC support, also tests do not run successfully in Firefox for now
#if CONFIG['MOZ_BUILD_APP'] != 'mobile':
# DIRS += ['notification']

View File

@ -629,6 +629,16 @@ void StartForceQuitWatchdog(ShutdownMode aMode, int32_t aTimeoutSecs)
PROXY_IF_SANDBOXED(StartForceQuitWatchdog(aMode, aTimeoutSecs));
}
void StartMonitoringGamepadStatus()
{
PROXY_IF_SANDBOXED(StartMonitoringGamepadStatus());
}
void StopMonitoringGamepadStatus()
{
PROXY_IF_SANDBOXED(StopMonitoringGamepadStatus());
}
void
RegisterWakeLockObserver(WakeLockObserver* aObserver)
{

View File

@ -559,6 +559,16 @@ void StartForceQuitWatchdog(hal::ShutdownMode aMode, int32_t aTimeoutSecs);
*/
void FactoryReset();
/**
* Start monitoring the status of gamepads attached to the system.
*/
void StartMonitoringGamepadStatus();
/**
* Stop monitoring the status of gamepads attached to the system.
*/
void StopMonitoringGamepadStatus();
} // namespace MOZ_HAL_NAMESPACE
} // namespace mozilla

View File

@ -42,6 +42,10 @@ CPPSRCS = \
HalWakeLock.cpp \
$(NULL)
ifeq (stub,$(MOZ_GAMEPAD_BACKEND))
CPPSRCS += FallbackGamepad.cpp
endif
ifeq (android,$(MOZ_WIDGET_TOOLKIT))
CPPSRCS += \
AndroidHal.cpp \

View File

@ -0,0 +1,19 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set sw=2 ts=8 et ft=cpp : */
/* 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 "Hal.h"
namespace mozilla {
namespace hal_impl {
void StartMonitoringGamepadStatus()
{}
void StopMonitoringGamepadStatus()
{}
} // hal_impl
} // namespace mozilla

View File

@ -263,6 +263,13 @@ DisableSensorNotifications(SensorType aSensor) {
Hal()->SendDisableSensorNotifications(aSensor);
}
//TODO: bug 852944 - IPC implementations of these
void StartMonitoringGamepadStatus()
{}
void StopMonitoringGamepadStatus()
{}
void
EnableWakeLockNotifications()
{

View File

@ -44,7 +44,12 @@ simple_events = [
'MozMmsEvent',
'DeviceStorageChangeEvent',
'PopupBlockedEvent',
'BlobEvent'
'BlobEvent',
#ifdef MOZ_GAMEPAD
'GamepadEvent',
'GamepadButtonEvent',
'GamepadAxisMoveEvent',
#endif
]
""" include file names """

View File

@ -217,6 +217,9 @@ static void Shutdown();
#include "nsGeolocation.h"
#include "nsDeviceSensors.h"
#ifdef MOZ_GAMEPAD
#include "mozilla/dom/GamepadService.h"
#endif
#include "nsCSPService.h"
#include "nsISmsService.h"
#include "nsIMobileMessageService.h"
@ -316,6 +319,11 @@ NS_GENERIC_FACTORY_SINGLETON_CONSTRUCTOR(nsIAlarmHalService,
AlarmHalService::GetInstance)
NS_GENERIC_FACTORY_SINGLETON_CONSTRUCTOR(nsITimeService,
TimeService::GetInstance)
#ifdef MOZ_GAMEPAD
using mozilla::dom::GamepadServiceTest;
NS_GENERIC_FACTORY_SINGLETON_CONSTRUCTOR(GamepadServiceTest,
GamepadServiceTest::CreateService)
#endif
#ifdef MOZ_WIDGET_GONK
NS_GENERIC_FACTORY_SINGLETON_CONSTRUCTOR(nsIGeolocationProvider,
@ -839,6 +847,9 @@ NS_DEFINE_NAMED_CID(NS_TIMESERVICE_CID);
NS_DEFINE_NAMED_CID(GONK_GPS_GEOLOCATION_PROVIDER_CID);
#endif
NS_DEFINE_NAMED_CID(NS_MEDIAMANAGERSERVICE_CID);
#ifdef MOZ_GAMEPAD
NS_DEFINE_NAMED_CID(NS_GAMEPAD_TEST_CID);
#endif
static nsresult
CreateWindowCommandTableConstructor(nsISupports *aOuter,
@ -1118,6 +1129,9 @@ static const mozilla::Module::CIDEntry kLayoutCIDs[] = {
{ &kGONK_GPS_GEOLOCATION_PROVIDER_CID, false, NULL, nsIGeolocationProviderConstructor },
#endif
{ &kNS_MEDIAMANAGERSERVICE_CID, false, NULL, nsIMediaManagerServiceConstructor },
#ifdef MOZ_GAMEPAD
{ &kNS_GAMEPAD_TEST_CID, false, NULL, GamepadServiceTestConstructor },
#endif
{ NULL }
};
@ -1259,6 +1273,9 @@ static const mozilla::Module::ContractIDEntry kLayoutContracts[] = {
{ TIMESERVICE_CONTRACTID, &kNS_TIMESERVICE_CID },
#ifdef MOZ_WIDGET_GONK
{ GONK_GPS_GEOLOCATION_PROVIDER_CONTRACTID, &kGONK_GPS_GEOLOCATION_PROVIDER_CID },
#endif
#ifdef MOZ_GAMEPAD
{ NS_GAMEPAD_TEST_CONTRACTID, &kNS_GAMEPAD_TEST_CID },
#endif
{ MEDIAMANAGERSERVICE_CONTRACTID, &kNS_MEDIAMANAGERSERVICE_CID },
{ NULL }

View File

@ -452,6 +452,18 @@ enum nsEventStructType {
#define NS_NETWORK_UPLOAD_EVENT (NS_NETWORK_EVENT_START + 1)
#define NS_NETWORK_DOWNLOAD_EVENT (NS_NETWORK_EVENT_START + 2)
#ifdef MOZ_GAMEPAD
// Gamepad input events
#define NS_GAMEPAD_START 6000
#define NS_GAMEPAD_BUTTONDOWN (NS_GAMEPAD_START)
#define NS_GAMEPAD_BUTTONUP (NS_GAMEPAD_START+1)
#define NS_GAMEPAD_AXISMOVE (NS_GAMEPAD_START+2)
#define NS_GAMEPAD_CONNECTED (NS_GAMEPAD_START+3)
#define NS_GAMEPAD_DISCONNECTED (NS_GAMEPAD_START+4)
// Keep this defined to the same value as the event above
#define NS_GAMEPAD_END (NS_GAMEPAD_START+4)
#endif
/**
* Return status for event processors, nsEventStatus, is defined in
* nsEvent.h.