Bug 679966, part 2: Add mozVibrator() for "playing" a vibration pattern. r=bz

This commit is contained in:
Justin Lebar 2011-11-10 16:26:36 -05:00
parent 9d36fab46a
commit 84241e428f
7 changed files with 354 additions and 10 deletions

View File

@ -71,6 +71,9 @@
#include "BatteryManager.h"
#include "SmsManager.h"
#include "nsISmsService.h"
#include "mozilla/Hal.h"
#include "nsIWebNavigation.h"
#include "mozilla/ClearOnShutdown.h"
// This should not be in the namespace.
DOMCI_DATA(Navigator, mozilla::dom::Navigator)
@ -79,7 +82,11 @@ namespace mozilla {
namespace dom {
static const char sJSStackContractID[] = "@mozilla.org/js/xpc/ContextStack;1";
bool Navigator::sDoNotTrackEnabled = false;
static bool sDoNotTrackEnabled = false;
static bool sVibratorEnabled = false;
static PRUint32 sMaxVibrateMS = 0;
static PRUint32 sMaxVibrateListLen = 0;
/* static */
void
@ -88,6 +95,12 @@ Navigator::Init()
Preferences::AddBoolVarCache(&sDoNotTrackEnabled,
"privacy.donottrackheader.enabled",
false);
Preferences::AddBoolVarCache(&sVibratorEnabled,
"dom.vibrator.enabled", true);
Preferences::AddUintVarCache(&sMaxVibrateMS,
"dom.vibrator.max_vibrate_ms", 10000);
Preferences::AddUintVarCache(&sMaxVibrateListLen,
"dom.vibrator.max_vibrate_list_len", 128);
}
Navigator::Navigator(nsPIDOMWindow* aWindow)
@ -527,6 +540,176 @@ Navigator::HasDesktopNotificationSupport()
return Preferences::GetBool("notification.feature.enabled", false);
}
namespace {
class VibrateWindowListener : public nsIDOMEventListener
{
public:
VibrateWindowListener(nsIDOMWindow *aWindow, nsIDOMDocument *aDocument)
{
mWindow = do_GetWeakReference(aWindow);
mDocument = do_GetWeakReference(aDocument);
nsCOMPtr<nsIDOMEventTarget> target = do_QueryInterface(aDocument);
NS_NAMED_LITERAL_STRING(visibilitychange, "mozvisibilitychange");
target->AddSystemEventListener(visibilitychange,
this, /* listener */
true, /* use capture */
false /* wants untrusted */);
}
virtual ~VibrateWindowListener()
{
}
void RemoveListener();
NS_DECL_ISUPPORTS
NS_DECL_NSIDOMEVENTLISTENER
private:
nsWeakPtr mWindow;
nsWeakPtr mDocument;
};
NS_IMPL_ISUPPORTS1(VibrateWindowListener, nsIDOMEventListener)
nsRefPtr<VibrateWindowListener> gVibrateWindowListener;
NS_IMETHODIMP
VibrateWindowListener::HandleEvent(nsIDOMEvent* aEvent)
{
nsCOMPtr<nsIDOMEventTarget> target;
aEvent->GetTarget(getter_AddRefs(target));
nsCOMPtr<nsIDOMDocument> doc = do_QueryInterface(target);
bool hidden = true;
if (doc) {
doc->GetMozHidden(&hidden);
}
if (hidden) {
// It's important that we call CancelVibrate(), not Vibrate() with an
// empty list, because Vibrate() will fail if we're no longer focused, but
// CancelVibrate() will succeed, so long as nobody else has started a new
// vibration pattern.
nsCOMPtr<nsIDOMWindow> window = do_QueryReferent(mWindow);
hal::CancelVibrate(window);
RemoveListener();
gVibrateWindowListener = NULL;
// Careful: The line above might have deleted |this|!
}
return NS_OK;
}
void
VibrateWindowListener::RemoveListener()
{
nsCOMPtr<nsIDOMEventTarget> target = do_QueryReferent(mDocument);
if (!target) {
return;
}
NS_NAMED_LITERAL_STRING(visibilitychange, "mozvisibilitychange");
target->RemoveSystemEventListener(visibilitychange, this,
true /* use capture */);
}
/**
* Converts a jsval into a vibration duration, checking that the duration is in
* bounds (non-negative and not larger than sMaxVibrateMS).
*
* Returns true on success, false on failure.
*/
bool
GetVibrationDurationFromJsval(const jsval& aJSVal, JSContext* cx,
PRInt32 *aOut)
{
return JS_ValueToInt32(cx, aJSVal, aOut) &&
*aOut >= 0 && static_cast<PRUint32>(*aOut) <= sMaxVibrateMS;
}
} // anonymous namespace
NS_IMETHODIMP
Navigator::MozVibrate(const jsval& aPattern, JSContext* cx)
{
nsCOMPtr<nsPIDOMWindow> win = do_QueryReferent(mWindow);
NS_ENSURE_TRUE(win, NS_OK);
nsIDOMDocument* domDoc = win->GetExtantDocument();
NS_ENSURE_TRUE(domDoc, NS_ERROR_FAILURE);
bool hidden = true;
domDoc->GetMozHidden(&hidden);
if (hidden) {
// Hidden documents cannot start or stop a vibration.
return NS_OK;
}
nsAutoTArray<PRUint32, 8> pattern;
// null or undefined pattern is an error.
if (JSVAL_IS_NULL(aPattern) || JSVAL_IS_VOID(aPattern)) {
return NS_ERROR_DOM_NOT_SUPPORTED_ERR;
}
if (JSVAL_IS_PRIMITIVE(aPattern)) {
PRInt32 p;
if (GetVibrationDurationFromJsval(aPattern, cx, &p)) {
pattern.AppendElement(p);
}
else {
return NS_ERROR_DOM_NOT_SUPPORTED_ERR;
}
}
else {
JSObject *obj = JSVAL_TO_OBJECT(aPattern);
PRUint32 length;
if (!JS_GetArrayLength(cx, obj, &length) || length > sMaxVibrateListLen) {
return NS_ERROR_DOM_NOT_SUPPORTED_ERR;
}
pattern.SetLength(length);
for (PRUint32 i = 0; i < length; ++i) {
jsval v;
PRInt32 pv;
if (JS_GetElement(cx, obj, i, &v) &&
GetVibrationDurationFromJsval(v, cx, &pv)) {
pattern[i] = pv;
}
else {
return NS_ERROR_DOM_NOT_SUPPORTED_ERR;
}
}
}
// The spec says we check sVibratorEnabled after we've done the sanity
// checking on the pattern.
if (!sVibratorEnabled) {
return NS_OK;
}
// Add a listener to cancel the vibration if the document becomes hidden,
// and remove the old mozvisibility listener, if there was one.
if (!gVibrateWindowListener) {
// If gVibrateWindowListener is null, this is the first time we've vibrated,
// and we need to register a listener to clear gVibrateWindowListener on
// shutdown.
ClearOnShutdown(&gVibrateWindowListener);
}
else {
gVibrateWindowListener->RemoveListener();
}
gVibrateWindowListener = new VibrateWindowListener(win, domDoc);
nsCOMPtr<nsIDOMWindow> domWindow =
do_QueryInterface(static_cast<nsIDOMWindow*>(win));
hal::Vibrate(pattern, domWindow);
return NS_OK;
}
//*****************************************************************************
// Navigator::nsIDOMClientInformation
//*****************************************************************************

View File

@ -107,8 +107,6 @@ private:
bool IsSmsAllowed() const;
bool IsSmsSupported() const;
static bool sDoNotTrackEnabled;
nsRefPtr<nsMimeTypeArray> mMimeTypes;
nsRefPtr<nsPluginArray> mPlugins;
nsRefPtr<nsGeolocation> mGeolocation;

View File

@ -39,7 +39,7 @@
#include "domstubs.idl"
[scriptable, uuid(B8EE0374-5F47-4ED0-B9B0-BDE3E6D81FF5)]
[scriptable, uuid(a1ee08c1-0299-4908-a6ba-7cBc8da6531f)]
interface nsIDOMNavigator : nsISupports
{
readonly attribute DOMString appCodeName;
@ -61,5 +61,52 @@ interface nsIDOMNavigator : nsISupports
readonly attribute DOMString doNotTrack;
boolean javaEnabled();
};
/**
* Pulse the device's vibrator, if it has one. If the device does not have a
* vibrator, this function does nothing. If the window is hidden, this
* function does nothing.
*
* mozVibrate takes one argument, which specifies either how long to vibrate
* for or gives a pattern of vibrator-on/vibrator-off timings.
*
* If a vibration pattern is in effect when this function is called, this
* call will overwrite the existing pattern, if this call successfully
* completes.
*
* We handle the argument to mozVibrate as follows.
*
* - If the argument is undefined or null, we throw
* NS_ERROR_DOM_NOT_SUPPORTED_ERR.
*
* - If the argument is 0, the empty list, or a list containing entirely 0s,
* we cancel any outstanding vibration pattern; that is, we stop the device
* from vibrating.
*
* - Otherwise, if the argument X is not a list, we treat it as though it's
* the singleton list [X] and then proceed as below.
*
* - If the argument is a list (or if we wrapped it as a list above), then we
* try to convert each element in the list to an integer, by first
* converting it to a number and then rounding. If there is some element
* that we can't convert to an integer, or if any of the integers are
* negative, we throw NS_ERROR_DOM_NOT_SUPPORTED_ERR.
*
* This list of integers specifies a vibration pattern. Given a list of
* numbers
*
* [a_1, b_1, a_2, b_2, ..., a_n]
*
* the device will vibrate for a_1 milliseconds, then be still for b_1
* milliseconds, then vibrate for a_2 milliseconds, and so on.
*
* The list may contain an even or an odd number of elements, but if you
* pass an even number of elements (that is, if your list ends with b_n
* instead of a_n), the final element doesn't specify anything meaningful.
*
* We may throw NS_ERROR_DOM_NOT_SUPPORTED_ERR if the vibration pattern is
* too long, or if any of its elements is too large.
*/
[implicit_jscontext]
void mozVibrate(in jsval aPattern);
};

View File

@ -72,6 +72,7 @@ _TEST_FILES = \
test_windowedhistoryframes.html \
test_focusrings.xul \
file_moving_xhr.html \
test_vibrator.html \
$(NULL)
_CHROME_FILES = \

View File

@ -0,0 +1,99 @@
<!DOCTYPE HTML>
<html>
<head>
<title>Test for Vibrator</title>
<script type="text/javascript" src="/MochiKit/MochiKit.js"></script>
<script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
</head>
<body>
<!-- Although we can't test that the vibrator works properly, we can test that
navigator.mozVibrate throws an exception where appropriate. -->
<script class="testbody" type="text/javascript;version=1.7">
function expectFailure(param) {
try {
navigator.mozVibrate(param);
}
catch(e) {
ok(true, 'mozVibrate(' + param + ') threw an expected exception.');
return;
}
ok(false, 'mozVibrate(' + param + ') should have thrown an exception.');
}
function expectSuccess(param) {
try {
navigator.mozVibrate(param);
}
catch(e) {
ok(false, 'mozVibrate(' + param + ') threw an unexpected exception.');
return;
}
ok(true, 'mozVibrate(' + param + ') did not throw an exception.');
}
function testFailures() {
expectFailure(null);
expectFailure(undefined);
expectFailure(-1);
expectFailure('a');
expectFailure([100, -1]);
expectFailure([100, 'a']);
var maxVibrateMs = SpecialPowers.getIntPref('dom.vibrator.max_vibrate_ms');
var maxVibrateListLen = SpecialPowers.getIntPref('dom.vibrator.max_vibrate_list_len');
// Make sure that these preferences are respected.
expectFailure(maxVibrateMs + 1);
expectFailure([maxVibrateMs + 1]);
var arr = [];
for (var i = 0; i < maxVibrateListLen + 1; i++) {
arr[i] = 0;
}
expectFailure(arr);
}
function testSuccesses() {
expectSuccess(0);
expectSuccess([]);
expectSuccess('1000');
expectSuccess(1000);
expectSuccess(1000.1);
expectSuccess([0, 0, 0]);
expectSuccess(['1000', 1000]);
expectSuccess([1000, 1000]);
expectSuccess([1000, 1000.1]);
// The following loop shouldn't cause us to crash. See bug 701716.
for (var i = 0; i < 10000; i++) {
navigator.mozVibrate([100, 100]);
}
ok(true, "Didn't crash after issuing a lot of vibrate() calls.");
}
var origVibratorEnabled = SpecialPowers.getBoolPref('dom.vibrator.enabled');
// Test with the vibrator pref enabled.
try {
SpecialPowers.setBoolPref('dom.vibrator.enabled', true);
testFailures();
testSuccesses();
// Everything should be the same when the vibrator is disabled -- in
// particular, a disabled vibrator shouldn't eat failures we'd otherwise
// observe.
SpecialPowers.setBoolPref('dom.vibrator.enabled', false);
testFailures();
testSuccesses();
}
finally {
SpecialPowers.setBoolPref('dom.vibrator.enabled', origVibratorEnabled);
}
</script>
</body>
</html>

View File

@ -50,16 +50,28 @@ Vibrate(const nsTArray<uint32> &pattern, const WindowIdentifier &)
// hal_sandbox::Vibrate, and hal_impl::Vibrate all must have the same
// signature.
// Strangely enough, the Android Java API seems to treat vibrate([0]) as a
// nop. But we want to treat vibrate([0]) like CancelVibrate! (Note that we
// also need to treat vibrate([]) as a call to CancelVibrate.)
bool allZero = true;
for (uint32 i = 0; i < pattern.Length(); i++) {
if (pattern[i] != 0) {
allZero = false;
break;
}
}
if (allZero) {
hal_impl::CancelVibrate(WindowIdentifier());
return;
}
AndroidBridge* b = AndroidBridge::Bridge();
if (!b) {
return;
}
if (pattern.Length() == 0) {
b->CancelVibrate();
} else {
b->Vibrate(pattern);
}
b->Vibrate(pattern);
}
void

View File

@ -3394,6 +3394,10 @@ pref("dom.event.handling-user-input-time-limit", 1000);
//3D Transforms
pref("layout.3d-transforms.enabled", true);
pref("dom.vibrator.enabled", true);
pref("dom.vibrator.max_vibrate_ms", 10000);
pref("dom.vibrator.max_vibrate_list_len", 128);
// Battery API
pref("dom.battery.enabled", true);