Bug 679966, part 1: Add vibrator support for android. r=blassey,cjones
authorChris Jones <jones.chris.g@gmail.com>
Fri, 30 Sep 2011 00:00:48 -0700
changeset 79983 ebb4fe2d4c83dbbc018464b66c76a7a0d65b5513
parent 79982 b23b73290e38e36ff5da26992ea6ff745ea7c697
child 79984 c170948e646eb24f0ed60cc91c4ae61ec3520af8
child 79986 95efc21bf5af830e5df9fe3ac571d305907c3221
push idunknown
push userunknown
push dateunknown
reviewersblassey, cjones
bugs679966
milestone10.0a1
Bug 679966, part 1: Add vibrator support for android. r=blassey,cjones
content/base/src/Makefile.in
content/events/src/Makefile.in
dom/ipc/ContentChild.cpp
dom/ipc/ContentChild.h
dom/ipc/ContentParent.cpp
dom/ipc/PContent.ipdl
dom/ipc/TabChild.h
dom/ipc/TabParent.cpp
dom/ipc/TabParent.h
embedding/android/AndroidManifest.xml.in
embedding/android/GeckoAppShell.java
hal/Hal.cpp
hal/Hal.h
hal/android/AndroidHal.cpp
hal/fallback/FallbackHal.cpp
hal/linux/LinuxHal.cpp
hal/sandbox/PHal.ipdl
hal/sandbox/SandboxHal.cpp
widget/src/android/AndroidBridge.cpp
widget/src/android/AndroidBridge.h
--- a/content/base/src/Makefile.in
+++ b/content/base/src/Makefile.in
@@ -59,16 +59,17 @@ EXPORTS		= \
 		nsNodeUtils.h \
 		nsPropertyTable.h \
 		nsScriptLoader.h \
 		nsStubDocumentObserver.h \
 		nsStubImageDecoderObserver.h \
 		nsStubMutationObserver.h \
 		nsTextFragment.h \
 		mozAutoDocUpdate.h \
+		nsFrameMessageManager.h \
 		$(NULL)
 
 EXPORTS_NAMESPACES = mozilla/dom
 
 EXPORTS_mozilla/dom = \
   Link.h \
   $(NULL)
 
--- a/content/events/src/Makefile.in
+++ b/content/events/src/Makefile.in
@@ -44,16 +44,17 @@ include $(DEPTH)/config/autoconf.mk
 
 MODULE		= content
 LIBRARY_NAME	= gkconevents_s
 LIBXUL_LIBRARY  = 1
 
 EXPORTS		= \
 		nsEventStateManager.h \
 		nsEventListenerManager.h \
+		nsDOMEventTargetHelper.h \
 		$(NULL)
 		
 CPPSRCS		= \
 		nsEventListenerManager.cpp \
 		nsEventStateManager.cpp \
 		nsDOMEvent.cpp \
 		nsDOMDataContainerEvent.cpp \
 		nsDOMUIEvent.cpp \
--- a/dom/ipc/ContentChild.cpp
+++ b/dom/ipc/ContentChild.cpp
@@ -228,16 +228,17 @@ ConsoleListener::Observe(nsIConsoleMessa
     return NS_OK;
 }
 
 ContentChild* ContentChild::sSingleton;
 
 ContentChild::ContentChild()
 #ifdef ANDROID
  : mScreenSize(0, 0)
+ , mID(PRUint64(-1))
 #endif
 {
 }
 
 ContentChild::~ContentChild()
 {
     delete gIndexedDBPath;
     gIndexedDBPath = nsnull;
@@ -798,10 +799,20 @@ ContentChild::RecvCycleCollect()
 bool
 ContentChild::RecvAppInfo(const nsCString& version, const nsCString& buildID)
 {
     mAppInfo.version.Assign(version);
     mAppInfo.buildID.Assign(buildID);
     return true;
 }
 
+bool
+ContentChild::RecvSetID(const PRUint64 &id)
+{
+    if (mID != PRUint64(-1)) {
+        NS_WARNING("Setting content child's ID twice?");
+    }
+    mID = id;
+    return true;
+}
+
 } // namespace dom
 } // namespace mozilla
--- a/dom/ipc/ContentChild.h
+++ b/dom/ipc/ContentChild.h
@@ -163,25 +163,28 @@ public:
     virtual bool RecvFlushMemory(const nsString& reason);
 
     virtual bool RecvActivateA11y();
 
     virtual bool RecvGarbageCollect();
     virtual bool RecvCycleCollect();
 
     virtual bool RecvAppInfo(const nsCString& version, const nsCString& buildID);
+    virtual bool RecvSetID(const PRUint64 &id);
 
 #ifdef ANDROID
     gfxIntSize GetScreenSize() { return mScreenSize; }
 #endif
 
     // Get the directory for IndexedDB files. We query the parent for this and
     // cache the value
     nsString &GetIndexedDBPath();
 
+    PRUint64 GetID() { return mID; }
+
 private:
     NS_OVERRIDE
     virtual void ActorDestroy(ActorDestroyReason why);
 
     NS_OVERRIDE
     virtual void ProcessingError(Result what);
 
     /**
@@ -193,16 +196,25 @@ private:
     InfallibleTArray<nsAutoPtr<AlertObserver> > mAlertObservers;
     nsRefPtr<ConsoleListener> mConsoleListener;
 #ifdef ANDROID
     gfxIntSize mScreenSize;
 #endif
 
     AppInfo mAppInfo;
 
+    /**
+     * An ID unique to the process containing our corresponding
+     * content parent.
+     *
+     * We expect our content parent to set this ID immediately after opening a
+     * channel to us.
+     */
+    PRUint64 mID;
+
     static ContentChild* sSingleton;
 
     DISALLOW_EVIL_CONSTRUCTORS(ContentChild);
 };
 
 } // namespace dom
 } // namespace mozilla
 
--- a/dom/ipc/ContentParent.cpp
+++ b/dom/ipc/ContentParent.cpp
@@ -157,16 +157,19 @@ MemoryReportRequestParent::Recv__delete_
 
 MemoryReportRequestParent::~MemoryReportRequestParent()
 {
     MOZ_COUNT_DTOR(MemoryReportRequestParent);
 }
 
 nsTArray<ContentParent*>* ContentParent::gContentParents;
 
+// The first content child has ID 1, so the chrome process can have ID 0.
+static PRUint64 gContentChildID = 1;
+
 ContentParent*
 ContentParent::GetNewOrUsed()
 {
     if (!gContentParents)
         gContentParents = new nsTArray<ContentParent*>();
 
     PRInt32 maxContentProcesses = Preferences::GetInt("dom.ipc.processCount", 1);
     if (maxContentProcesses < 1)
@@ -419,16 +422,17 @@ ContentParent::ContentParent()
     , mShouldCallUnblockChild(false)
     , mIsAlive(true)
     , mSendPermissionUpdates(false)
 {
     NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
     mSubprocess = new GeckoChildProcessHost(GeckoProcessType_Content);
     mSubprocess->AsyncLaunch();
     Open(mSubprocess->GetChannel(), mSubprocess->GetChildProcessHandle());
+    unused << SendSetID(gContentChildID++);
 
     nsCOMPtr<nsIChromeRegistry> registrySvc = nsChromeRegistry::GetService();
     nsChromeRegistryChrome* chromeRegistry =
         static_cast<nsChromeRegistryChrome*>(registrySvc.get());
     chromeRegistry->SendRegisteredChrome(this);
     mMessageManager = nsFrameMessageManager::NewProcessMessageManager(this);
 
     if (gAppData) {
--- a/dom/ipc/PContent.ipdl
+++ b/dom/ipc/PContent.ipdl
@@ -142,16 +142,18 @@ child:
     
     /**
      * Start accessibility engine in content process.
      */
     ActivateA11y();
 
     AppInfo(nsCString version, nsCString buildID);
 
+    SetID(PRUint64 id);
+
 parent:
     PAudio(PRInt32 aNumChannels, PRInt32 aRate, PRInt32 aFormat);
 
     sync PCrashReporter(NativeThreadId tid, PRUint32 processType);
 
     PHal();
 
     PNecko();
--- a/dom/ipc/TabChild.h
+++ b/dom/ipc/TabChild.h
@@ -298,12 +298,20 @@ GetTabChildFrom(nsIPresShell* aPresShell
     if (!doc) {
         return nsnull;
     }
     nsCOMPtr<nsISupports> container = doc->GetContainer();
     nsCOMPtr<nsIDocShell> docShell(do_QueryInterface(container));
     return GetTabChildFrom(docShell);
 }
 
+inline TabChild*
+GetTabChildFrom(nsIDOMWindow* aWindow)
+{
+    nsCOMPtr<nsIWebNavigation> webNav = do_GetInterface(aWindow);
+    nsCOMPtr<nsIDocShell> docShell = do_QueryInterface(webNav);
+    return GetTabChildFrom(docShell);
+}
+
 }
 }
 
 #endif // mozilla_dom_TabChild_h
--- a/dom/ipc/TabParent.cpp
+++ b/dom/ipc/TabParent.cpp
@@ -86,16 +86,17 @@ TabParent *TabParent::mIMETabParent = ns
 
 NS_IMPL_ISUPPORTS3(TabParent, nsITabParent, nsIAuthPromptProvider, nsISecureBrowserUI)
 
 TabParent::TabParent()
   : mIMEComposing(false)
   , mIMECompositionEnding(false)
   , mIMESeqno(0)
   , mDPI(0)
+  , mActive(false)
 {
 }
 
 TabParent::~TabParent()
 {
 }
 
 void
@@ -212,25 +213,33 @@ void
 TabParent::UpdateDimensions(const nsRect& rect, const nsIntSize& size)
 {
     unused << SendUpdateDimensions(rect, size);
 }
 
 void
 TabParent::Activate()
 {
+    mActive = true;
     unused << SendActivate();
 }
 
 void
 TabParent::Deactivate()
 {
+  mActive = false;
   unused << SendDeactivate();
 }
 
+bool
+TabParent::Active()
+{
+  return mActive;
+}
+
 NS_IMETHODIMP
 TabParent::Init(nsIDOMWindow *window)
 {
   return NS_OK;
 }
 
 NS_IMETHODIMP
 TabParent::GetState(PRUint32 *aState)
--- a/dom/ipc/TabParent.h
+++ b/dom/ipc/TabParent.h
@@ -126,16 +126,23 @@ public:
     void LoadURL(nsIURI* aURI);
     // XXX/cjones: it's not clear what we gain by hiding these
     // message-sending functions under a layer of indirection and
     // eating the return values
     void Show(const nsIntSize& size);
     void UpdateDimensions(const nsRect& rect, const nsIntSize& size);
     void Activate();
     void Deactivate();
+
+    /**
+     * Is this object active?  That is, was Activate() called more recently than
+     * Deactivate()?
+     */
+    bool Active();
+
     void SendMouseEvent(const nsAString& aType, float aX, float aY,
                         PRInt32 aButton, PRInt32 aClickCount,
                         PRInt32 aModifiers, bool aIgnoreRootScrollFrame);
     void SendKeyEvent(const nsAString& aType, PRInt32 aKeyCode,
                       PRInt32 aCharCode, PRInt32 aModifiers,
                       bool aPreventDefault);
     bool SendRealMouseEvent(nsMouseEvent& event);
     bool SendMouseScrollEvent(nsMouseScrollEvent& event);
@@ -216,16 +223,17 @@ protected:
     bool mIMECompositionEnding;
     // Buffer to store composition text during ResetInputState
     // Compositions in almost all cases are small enough for nsAutoString
     nsAutoString mIMECompositionText;
     PRUint32 mIMECompositionStart;
     PRUint32 mIMESeqno;
 
     float mDPI;
+    bool mActive;
 
 private:
     already_AddRefed<nsFrameLoader> GetFrameLoader() const;
     already_AddRefed<nsIWidget> GetWidget() const;
 };
 
 } // namespace dom
 } // namespace mozilla
--- a/embedding/android/AndroidManifest.xml.in
+++ b/embedding/android/AndroidManifest.xml.in
@@ -17,16 +17,17 @@
     <uses-permission android:name="android.permission.INTERNET"/>
     <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
     <uses-permission android:name="com.android.launcher.permission.INSTALL_SHORTCUT"/>
 
     <uses-permission android:name="android.permission.READ_LOGS"/>
     <uses-permission android:name="android.permission.WAKE_LOCK"/>
     <uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/>
     <uses-permission android:name="android.permission.CHANGE_NETWORK_STATE"/> 
+    <uses-permission android:name="android.permission.VIBRATE"/>
 
     <uses-feature android:name="android.hardware.location" android:required="false"/>
     <uses-feature android:name="android.hardware.location.gps" android:required="false"/>
     <uses-feature android:name="android.hardware.touchscreen"/>
 
     <uses-permission android:name="android.permission.CAMERA" />
     <uses-feature android:name="android.hardware.camera" android:required="false"/>
     <uses-feature android:name="android.hardware.camera.autofocus" android:required="false"/>
--- a/embedding/android/GeckoAppShell.java
+++ b/embedding/android/GeckoAppShell.java
@@ -1038,16 +1038,32 @@ public class GeckoAppShell
 
     public static void performHapticFeedback(boolean aIsLongPress) {
         GeckoApp.surfaceView.
             performHapticFeedback(aIsLongPress ?
                                   HapticFeedbackConstants.LONG_PRESS :
                                   HapticFeedbackConstants.VIRTUAL_KEY);
     }
 
+    private static Vibrator vibrator() {
+        return (Vibrator) GeckoApp.surfaceView.getContext().getSystemService(Context.VIBRATOR_SERVICE);
+    }
+
+    public static void vibrate(long milliseconds) {
+        vibrator().vibrate(milliseconds);
+    }
+
+    public static void vibrate(long[] pattern, int repeat) {
+        vibrator().vibrate(pattern, repeat);
+    }
+
+    public static void cancelVibrate() {
+        vibrator().cancel();
+    }
+
     public static void showInputMethodPicker() {
         InputMethodManager imm = (InputMethodManager) GeckoApp.surfaceView.getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
         imm.showInputMethodPicker();
     }
 
     public static void hideProgressDialog() {
         GeckoApp.surfaceView.mShowingSplashScreen = false;
     }
--- a/hal/Hal.cpp
+++ b/hal/Hal.cpp
@@ -1,10 +1,10 @@
 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
-/* vim: sw=2 ts=8 et ft=cpp : */
+/* vim: set sw=2 ts=8 et ft=cpp : */
 /* ***** BEGIN LICENSE BLOCK *****
  * Version: MPL 1.1/GPL 2.0/LGPL 2.1
  *
  * The contents of this file are subject to the Mozilla Public License Version
  * 1.1 (the "License"); you may not use this file except in compliance with
  * the License. You may obtain a copy of the License at:
  * http://www.mozilla.org/MPL/
  *
@@ -33,52 +33,256 @@
  * decision by deleting the provisions above and replace them with the notice
  * and other provisions required by the GPL or the LGPL. If you do not delete
  * the provisions above, a recipient may use your version of this file under
  * the terms of any one of the MPL, the GPL or the LGPL.
  *
  * ***** END LICENSE BLOCK ***** */
 
 #include "Hal.h"
+#include "mozilla/dom/ContentChild.h"
+#include "mozilla/dom/TabChild.h"
 #include "mozilla/Util.h"
 #include "nsThreadUtils.h"
 #include "nsXULAppAPI.h"
 #include "mozilla/Observer.h"
+#include "nsIDOMDocument.h"
+#include "nsIDOMWindow.h"
+#include "nsPIDOMWindow.h"
+#include "nsIObserver.h"
+#include "nsIObserverService.h"
+#include "mozilla/Services.h"
+#include "nsIWebNavigation.h"
+#include "nsITabChild.h"
+#include "nsIDocShell.h"
+
+using namespace mozilla::dom;
+using namespace mozilla::services;
 
 #define PROXY_IF_SANDBOXED(_call)                 \
   do {                                            \
     if (InSandbox()) {                            \
       hal_sandbox::_call;                         \
     } else {                                      \
       hal_impl::_call;                            \
     }                                             \
   } while (0)
 
 namespace mozilla {
 namespace hal {
 
-static void
+PRLogModuleInfo *sHalLog = PR_LOG_DEFINE("hal");
+
+namespace {
+
+void
 AssertMainThread()
 {
   MOZ_ASSERT(NS_IsMainThread());
 }
 
-static bool
+bool
 InSandbox()
 {
   return GeckoProcessType_Content == XRE_GetProcessType();
 }
 
+bool
+WindowIsActive(nsIDOMWindow *window)
+{
+  NS_ENSURE_TRUE(window, false);
+
+  nsCOMPtr<nsIDOMDocument> doc;
+  window->GetDocument(getter_AddRefs(doc));
+  NS_ENSURE_TRUE(doc, false);
+
+  bool hidden = true;
+  doc->GetMozHidden(&hidden);
+  return !hidden;
+}
+
+nsAutoPtr<WindowIdentifier::IDArrayType> gLastIDToVibrate;
+
+// This observer makes sure we delete gLastIDToVibrate, so we don't
+// leak.
+class ShutdownObserver : public nsIObserver
+{
+public:
+  ShutdownObserver() {}
+  virtual ~ShutdownObserver() {}
+
+  NS_DECL_ISUPPORTS
+
+  NS_IMETHOD Observe(nsISupports *subject, const char *aTopic,
+                     const PRUnichar *aData)
+  {
+    MOZ_ASSERT(strcmp(aTopic, NS_XPCOM_SHUTDOWN_OBSERVER_ID) == 0);
+    gLastIDToVibrate = nsnull;
+    return NS_OK;
+  }
+};
+
+NS_IMPL_ISUPPORTS1(ShutdownObserver, nsIObserver);
+
+void InitLastIDToVibrate()
+{
+  gLastIDToVibrate = new WindowIdentifier::IDArrayType();
+
+  nsCOMPtr<nsIObserverService> observerService = GetObserverService();
+  if (!observerService) {
+    NS_WARNING("Could not get observer service!");
+    return;
+  }
+
+  ShutdownObserver *obs = new ShutdownObserver();
+  observerService->AddObserver(obs, NS_XPCOM_SHUTDOWN_OBSERVER_ID, false);
+}
+
+} // anonymous namespace
+
+WindowIdentifier::WindowIdentifier()
+  : mWindow(nsnull)
+  , mIsEmpty(true)
+{
+}
+
+WindowIdentifier::WindowIdentifier(nsIDOMWindow *window)
+  : mWindow(window)
+  , mIsEmpty(false)
+{
+  mID.AppendElement(GetWindowID());
+}
+
+WindowIdentifier::WindowIdentifier(nsCOMPtr<nsIDOMWindow> &window)
+  : mWindow(window)
+  , mIsEmpty(false)
+{
+  mID.AppendElement(GetWindowID());
+}
+
+WindowIdentifier::WindowIdentifier(const nsTArray<uint64> &id, nsIDOMWindow *window)
+  : mID(id)
+  , mWindow(window)
+  , mIsEmpty(false)
+{
+  mID.AppendElement(GetWindowID());
+}
+
+WindowIdentifier::WindowIdentifier(const WindowIdentifier &other)
+  : mID(other.mID)
+  , mWindow(other.mWindow)
+  , mIsEmpty(other.mIsEmpty)
+{
+}
+
+const InfallibleTArray<uint64>&
+WindowIdentifier::AsArray() const
+{
+  MOZ_ASSERT(!mIsEmpty);
+  return mID;
+}
+
+bool
+WindowIdentifier::HasTraveledThroughIPC() const
+{
+  MOZ_ASSERT(!mIsEmpty);
+  return mID.Length() >= 2;
+}
+
 void
-Vibrate(const nsTArray<uint32>& pattern)
+WindowIdentifier::AppendProcessID()
+{
+  MOZ_ASSERT(!mIsEmpty);
+  mID.AppendElement(ContentChild::GetSingleton()->GetID());
+}
+
+uint64
+WindowIdentifier::GetWindowID() const
+{
+  MOZ_ASSERT(!mIsEmpty);
+  nsCOMPtr<nsPIDOMWindow> pidomWindow = do_QueryInterface(mWindow);
+  if (!pidomWindow) {
+    return uint64(-1);
+  }
+  return pidomWindow->WindowID();
+}
+
+nsIDOMWindow*
+WindowIdentifier::GetWindow() const
+{
+  MOZ_ASSERT(!mIsEmpty);
+  return mWindow;
+}
+
+void
+Vibrate(const nsTArray<uint32>& pattern, const WindowIdentifier &id)
 {
   AssertMainThread();
-  PROXY_IF_SANDBOXED(Vibrate(pattern));
+
+  // Only active windows may start vibrations.  If |id| hasn't gone
+  // through the IPC layer -- that is, if our caller is the outside
+  // world, not hal_proxy -- check whether the window is active.  If
+  // |id| has gone through IPC, don't check the window's visibility;
+  // only the window corresponding to the bottommost process has its
+  // visibility state set correctly.
+  if (!id.HasTraveledThroughIPC() && !WindowIsActive(id.GetWindow())) {
+    HAL_LOG(("Vibrate: Window is inactive, dropping vibrate."));
+    return;
+  }
+
+  if (InSandbox()) {
+    hal_sandbox::Vibrate(pattern, id);
+  }
+  else {
+    if (!gLastIDToVibrate)
+      InitLastIDToVibrate();
+    *gLastIDToVibrate = id.AsArray();
+
+    HAL_LOG(("Vibrate: Forwarding to hal_impl."));
+
+    // hal_impl doesn't need |id|. Send it an empty id, which will
+    // assert if it's used.
+    hal_impl::Vibrate(pattern, WindowIdentifier());
+  }
 }
 
+void
+CancelVibrate(const WindowIdentifier &id)
+{
+  AssertMainThread();
+
+  // Although only active windows may start vibrations, a window may
+  // cancel its own vibration even if it's no longer active.
+  //
+  // After a window is marked as inactive, it sends a CancelVibrate
+  // request.  We want this request to cancel a playing vibration
+  // started by that window, so we certainly don't want to reject the
+  // cancellation request because the window is now inactive.
+  //
+  // But it could be the case that, after this window became inactive,
+  // some other window came along and started a vibration.  We don't
+  // want this window's cancellation request to cancel that window's
+  // actively-playing vibration!
+  //
+  // To solve this problem, we keep track of the id of the last window
+  // to start a vibration, and only accepts cancellation requests from
+  // the same window.  All other cancellation requests are ignored.
+
+  if (InSandbox()) {
+    hal_sandbox::CancelVibrate(id);
+  }
+  else if (*gLastIDToVibrate == id.AsArray()) {
+    // Don't forward our ID to hal_impl. It doesn't need it, and we
+    // don't want it to be tempted to read it.  The empty identifier
+    // will assert if it's used.
+    HAL_LOG(("CancelVibrate: Forwarding to hal_impl."));
+    hal_impl::CancelVibrate(WindowIdentifier());
+  }
+}
+ 
 class BatteryObserversManager
 {
 public:
   void AddObserver(BatteryObserver* aObserver) {
     if (!mObservers) {
       mObservers = new ObserverList<BatteryInformation>();
     }
 
--- a/hal/Hal.h
+++ b/hal/Hal.h
@@ -1,10 +1,10 @@
 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
-/* vim: sw=2 ts=8 et ft=cpp : */
+/* vim: set sw=2 ts=8 et ft=cpp : */
 /* ***** BEGIN LICENSE BLOCK *****
  * Version: MPL 1.1/GPL 2.0/LGPL 2.1
  *
  * The contents of this file are subject to the Mozilla Public License Version
  * 1.1 (the "License"); you may not use this file except in compliance with
  * the License. You may obtain a copy of the License at:
  * http://www.mozilla.org/MPL/
  *
@@ -38,19 +38,130 @@
  * ***** END LICENSE BLOCK ***** */
 
 #ifndef mozilla_Hal_h
 #define mozilla_Hal_h 1
 
 #include "base/basictypes.h"
 #include "mozilla/Types.h"
 #include "nsTArray.h"
+#include "nsCOMPtr.h"
+#include "nsIDOMWindow.h"
+#include "prlog.h"
 #include "mozilla/dom/battery/Types.h"
 
 #ifndef MOZ_HAL_NAMESPACE
+
+namespace mozilla {
+namespace dom {
+class TabChild;
+class PBrowserChild;
+}
+}
+
+// Only include this hunk of code once, and include it before
+// HalImpl.h and HalSandbox.h.
+namespace mozilla {
+namespace hal {
+
+extern PRLogModuleInfo *sHalLog;
+#define HAL_LOG(msg) PR_LOG(sHalLog, PR_LOG_DEBUG, msg)
+
+/**
+ * This class serves two purposes.
+ *
+ * First, this class wraps a pointer to a window.
+ *
+ * Second, WindowIdentifier lets us uniquely identify a window across
+ * processes.  A window exposes an ID which is unique only within its
+ * process.  Thus to identify a window, we need to know the ID of the
+ * process which contains it.  But the scope of a process's ID is its
+ * parent; that is, two processes with different parents might have
+ * the same ID.
+ *
+ * So to identify a window, we need its ID plus the IDs of all the
+ * processes in the path from the window's process to the root
+ * process.  We throw in the IDs of the intermediate windows (a
+ * content window is contained in a window at each level of the
+ * process tree) for good measures.
+ *
+ * You can access this list of IDs by calling AsArray().
+ */
+class WindowIdentifier
+{
+public:
+  /**
+   * Create an empty WindowIdentifier.  Calls to any of this object's
+   * public methods will assert -- an empty WindowIdentifier may be
+   * used only as a placeholder to code which promises not to touch
+   * the object.
+   */
+  WindowIdentifier();
+
+  /**
+   * Copy constructor.
+   */
+  WindowIdentifier(const WindowIdentifier& other);
+
+  /**
+   * Wrap the given window in a WindowIdentifier.  These two
+   * constructors automatically grab the window's ID and append it to
+   * the array of IDs.
+   *
+   * Note that these constructors allow an implicit conversion to a
+   * WindowIdentifier.
+   */
+  WindowIdentifier(nsIDOMWindow* window);
+  WindowIdentifier(nsCOMPtr<nsIDOMWindow> &window);
+
+  /**
+   * Create a new WindowIdentifier with the given id array and window.
+   * This automatically grabs the window's ID and appends it to the
+   * array.
+   */
+  WindowIdentifier(const nsTArray<uint64>& id, nsIDOMWindow* window);
+
+  /**
+   * Get the list of window and process IDs we contain.
+   */
+  typedef InfallibleTArray<uint64> IDArrayType;
+  const IDArrayType& AsArray() const;
+
+  /**
+   * Append the ID of the ContentChild singleton to our array of
+   * window/process IDs.
+   */
+  void AppendProcessID();
+
+  /**
+   * Does this WindowIdentifier identify both a window and the process
+   * containing that window?  If so, we say it has traveled through
+   * IPC.
+   */
+  bool HasTraveledThroughIPC() const;
+
+  /**
+   * Get the window this object wraps.
+   */
+  nsIDOMWindow* GetWindow() const;
+
+private:
+  /**
+   * Get the ID of the window object we wrap.
+   */
+  uint64 GetWindowID() const;
+
+  AutoInfallibleTArray<uint64, 3> mID;
+  nsCOMPtr<nsIDOMWindow> mWindow;
+  bool mIsEmpty;
+};
+
+} // namespace hal
+} // namespace mozilla
+
 // This goop plays some cpp tricks to ensure a uniform API across the
 // API entry point, "sandbox" implementations (for content processes),
 // and "impl" backends where the real work happens.  After this runs
 // through cpp, there will be three sets of identical APIs
 //   hal_impl:: --- the platform-specific implementation of an API.
 //   hal_sandbox:: --- forwards calls up to the parent process
 //   hal:: --- invokes sandboxed impl if in a sandboxed process,
 //             otherwise forwards to hal_impl
@@ -73,18 +184,42 @@ namespace MOZ_HAL_NAMESPACE /*hal*/ {
 
 /**
  * Turn the default vibrator device on/off per the pattern specified
  * by |pattern|.  Each element in the pattern is the number of
  * milliseconds to turn the vibrator on or off.  The first element in
  * |pattern| is an "on" element, the next is "off", and so on.
  *
  * If |pattern| is empty, any in-progress vibration is canceled.
+ *
+ * Only an active window within an active tab may call Vibrate; calls
+ * from inactive windows and windows on inactive tabs do nothing.
+ *
+ * If you're calling hal::Vibrate from the outside world, pass an
+ * nsIDOMWindow* or an nsCOMPtr<nsIDOMWindow>& in place of the
+ * WindowIdentifier parameter.  It'll be converted to a WindowIdentifier
+ * automatically.
  */
-void Vibrate(const nsTArray<uint32>& pattern);
+void Vibrate(const nsTArray<uint32>& pattern,
+             const hal::WindowIdentifier &id);
+
+/**
+ * Cancel a vibration started by the content window identified by
+ * WindowIdentifier.
+ *
+ * If the window was the last window to start a vibration, the
+ * cancellation request will go through even if the window is not
+ * active.
+ *
+ * As with hal::Vibrate(), if you're calling hal::CancelVibrate from
+ * the outside world, pass an nsIDOMWindow* or an
+ * nsCOMPtr<nsIDOMWindow>&.  This will automatically be converted to a
+ * WindowIdentifier object.
+ */
+void CancelVibrate(const hal::WindowIdentifier &id);
 
 /**
  * Inform the battery backend there is a new battery observer.
  * @param aBatteryObserver The observer that should be added.
  */
 void RegisterBatteryObserver(BatteryObserver* aBatteryObserver);
 
 /**
@@ -117,17 +252,17 @@ void DisableBatteryNotifications();
 void GetCurrentBatteryInformation(hal::BatteryInformation* aBatteryInfo);
 
 /**
  * Notify of a change in the battery state.
  * @param aBatteryInfo The new battery information.
  */
 void NotifyBatteryChange(const hal::BatteryInformation& aBatteryInfo);
 
-}
-}
+} // namespace MOZ_HAL_NAMESPACE
+} // namespace mozilla
 
 #ifdef MOZ_DEFINED_HAL_NAMESPACE
 # undef MOZ_DEFINED_HAL_NAMESPACE
 # undef MOZ_HAL_NAMESPACE
 #endif
 
 #endif  // mozilla_Hal_h
--- a/hal/android/AndroidHal.cpp
+++ b/hal/android/AndroidHal.cpp
@@ -33,22 +33,49 @@
  * the provisions above, a recipient may use your version of this file under
  * the terms of any one of the MPL, the GPL or the LGPL.
  *
  * ***** END LICENSE BLOCK ***** */
 
 #include "Hal.h"
 #include "AndroidBridge.h"
 
+using mozilla::hal::WindowIdentifier;
+
 namespace mozilla {
 namespace hal_impl {
 
 void
-Vibrate(const nsTArray<uint32>& pattern)
-{}
+Vibrate(const nsTArray<uint32> &pattern, const WindowIdentifier &)
+{
+  // Ignore the WindowIdentifier parameter; it's here only because hal::Vibrate,
+  // hal_sandbox::Vibrate, and hal_impl::Vibrate all must have the same
+  // signature.
+
+  AndroidBridge* b = AndroidBridge::Bridge();
+  if (!b) {
+    return;
+  }
+
+  if (pattern.Length() == 0) {
+    b->CancelVibrate();
+  } else {
+    b->Vibrate(pattern);
+  }
+}
+
+void
+CancelVibrate(const WindowIdentifier &)
+{
+  // Ignore WindowIdentifier parameter.
+
+  AndroidBridge* b = AndroidBridge::Bridge();
+  if (b)
+    b->CancelVibrate();
+}
 
 void
 EnableBatteryNotifications()
 {
   AndroidBridge* bridge = AndroidBridge::Bridge();
   if (!bridge) {
     return;
   }
--- a/hal/fallback/FallbackHal.cpp
+++ b/hal/fallback/FallbackHal.cpp
@@ -35,21 +35,27 @@
  * the provisions above, a recipient may use your version of this file under
  * the terms of any one of the MPL, the GPL or the LGPL.
  *
  * ***** END LICENSE BLOCK ***** */
 
 #include "Hal.h"
 #include "mozilla/dom/battery/Constants.h"
 
+using mozilla::hal::WindowIdentifier;
+
 namespace mozilla {
 namespace hal_impl {
 
 void
-Vibrate(const nsTArray<uint32>& pattern)
+Vibrate(const nsTArray<uint32>& pattern, const hal::WindowIdentifier &)
+{}
+
+void
+CancelVibrate(const hal::WindowIdentifier &)
 {}
 
 void
 EnableBatteryNotifications()
 {}
 
 void
 DisableBatteryNotifications()
--- a/hal/linux/LinuxHal.cpp
+++ b/hal/linux/LinuxHal.cpp
@@ -40,17 +40,21 @@
 #ifndef MOZ_ENABLE_DBUS
 #include <mozilla/dom/battery/Constants.h>
 #endif // !MOZ_ENABLE_DBUS
 
 namespace mozilla {
 namespace hal_impl {
 
 void
-Vibrate(const nsTArray<uint32>& pattern)
+Vibrate(const nsTArray<uint32>& pattern, const hal::WindowIdentifier &)
+{}
+
+void
+CancelVibrate(const hal::WindowIdentifier &)
 {}
 
 #ifndef MOZ_ENABLE_DBUS
 void
 EnableBatteryNotifications()
 {}
 
 void
--- a/hal/sandbox/PHal.ipdl
+++ b/hal/sandbox/PHal.ipdl
@@ -33,16 +33,17 @@
  * decision by deleting the provisions above and replace them with the notice
  * and other provisions required by the GPL or the LGPL. If you do not delete
  * the provisions above, a recipient may use your version of this file under
  * the terms of any one of the MPL, the GPL or the LGPL.
  *
  * ***** END LICENSE BLOCK ***** */
 
 include protocol PContent;
+include protocol PBrowser;
 
 namespace mozilla {
 
 namespace hal {
   struct BatteryInformation {
     float level;
     bool  charging;
   };
@@ -52,17 +53,18 @@ namespace hal_sandbox {
 
 sync protocol PHal {
     manager PContent;
 
 child:
     NotifyBatteryChange(BatteryInformation aBatteryInfo);
 
 parent:
-    Vibrate(uint32[] pattern);
+    Vibrate(uint32[] pattern, uint64[] id, PBrowser browser);
+    CancelVibrate(uint64[] id, PBrowser browser);
 
     EnableBatteryNotifications();
     DisableBatteryNotifications();
     sync GetCurrentBatteryInformation()
       returns (BatteryInformation aBatteryInfo);
 
     __delete__();
 };
--- a/hal/sandbox/SandboxHal.cpp
+++ b/hal/sandbox/SandboxHal.cpp
@@ -36,16 +36,18 @@
  * the terms of any one of the MPL, the GPL or the LGPL.
  *
  * ***** END LICENSE BLOCK ***** */
 
 #include "Hal.h"
 #include "mozilla/dom/ContentChild.h"
 #include "mozilla/hal_sandbox/PHalChild.h"
 #include "mozilla/hal_sandbox/PHalParent.h"
+#include "mozilla/dom/TabParent.h"
+#include "mozilla/dom/TabChild.h"
 #include "mozilla/dom/battery/Types.h"
 #include "mozilla/Observer.h"
 #include "mozilla/unused.h"
 
 using namespace mozilla;
 using namespace mozilla::dom;
 using namespace mozilla::hal;
 
@@ -58,20 +60,35 @@ Hal()
 {
   if (!sHal) {
     sHal = ContentChild::GetSingleton()->SendPHalConstructor();
   }
   return sHal;
 }
 
 void
-Vibrate(const nsTArray<uint32>& pattern)
+Vibrate(const nsTArray<uint32>& pattern, const WindowIdentifier &id)
 {
+  HAL_LOG(("Vibrate: Sending to parent process."));
+
   AutoInfallibleTArray<uint32, 8> p(pattern);
-  Hal()->SendVibrate(p);
+
+  WindowIdentifier newID(id);
+  newID.AppendProcessID();
+  Hal()->SendVibrate(p, newID.AsArray(), GetTabChildFrom(newID.GetWindow()));
+}
+
+void
+CancelVibrate(const WindowIdentifier &id)
+{
+  HAL_LOG(("CancelVibrate: Sending to parent process."));
+
+  WindowIdentifier newID(id);
+  newID.AppendProcessID();
+  Hal()->SendCancelVibrate(newID.AsArray(), GetTabChildFrom(newID.GetWindow()));
 }
 
 void
 EnableBatteryNotifications()
 {
   Hal()->SendEnableBatteryNotifications();
 }
 
@@ -86,21 +103,51 @@ GetCurrentBatteryInformation(BatteryInfo
 {
   Hal()->SendGetCurrentBatteryInformation(aBatteryInfo);
 }
 
 class HalParent : public PHalParent
                 , public BatteryObserver {
 public:
   NS_OVERRIDE virtual bool
-  RecvVibrate(const InfallibleTArray<unsigned int>& pattern) {
+  RecvVibrate(const InfallibleTArray<unsigned int>& pattern,
+              const InfallibleTArray<uint64> &id,
+              PBrowserParent *browserParent)
+  {
+    // Check whether browserParent is active.  We should have already
+    // checked that the corresponding window is active, but this check
+    // isn't redundant.  A window may be inactive in an active
+    // browser.  And a window is not notified synchronously when it's
+    // deactivated, so the window may think it's active when the tab
+    // is actually inactive.
+    TabParent *tabParent = static_cast<TabParent*>(browserParent);
+    if (!tabParent->Active()) {
+      HAL_LOG(("RecvVibrate: Tab is not active. Cancelling."));
+      return true;
+    }
+
     // Forward to hal::, not hal_impl::, because we might be a
     // subprocess of another sandboxed process.  The hal:: entry point
     // will do the right thing.
-    hal::Vibrate(pattern);
+    nsCOMPtr<nsIDOMWindow> window =
+      do_QueryInterface(tabParent->GetBrowserDOMWindow());
+    WindowIdentifier newID(id, window);
+    hal::Vibrate(pattern, newID);
+    return true;
+  }
+
+  NS_OVERRIDE virtual bool
+  RecvCancelVibrate(const InfallibleTArray<uint64> &id,
+                    PBrowserParent *browserParent)
+  {
+    TabParent *tabParent = static_cast<TabParent*>(browserParent);
+    nsCOMPtr<nsIDOMWindow> window =
+      do_QueryInterface(tabParent->GetBrowserDOMWindow());
+    WindowIdentifier newID(id, window);
+    hal::CancelVibrate(newID);
     return true;
   }
 
   NS_OVERRIDE virtual bool
   RecvEnableBatteryNotifications() {
     hal::RegisterBatteryObserver(this);
     return true;
   }
--- a/widget/src/android/AndroidBridge.cpp
+++ b/widget/src/android/AndroidBridge.cpp
@@ -136,16 +136,19 @@ AndroidBridge::Init(JNIEnv *jEnv,
     jShowFilePicker = (jmethodID) jEnv->GetStaticMethodID(jGeckoAppShellClass, "showFilePicker", "(Ljava/lang/String;)Ljava/lang/String;");
     jAlertsProgressListener_OnProgress = (jmethodID) jEnv->GetStaticMethodID(jGeckoAppShellClass, "alertsProgressListener_OnProgress", "(Ljava/lang/String;JJLjava/lang/String;)V");
     jAlertsProgressListener_OnCancel = (jmethodID) jEnv->GetStaticMethodID(jGeckoAppShellClass, "alertsProgressListener_OnCancel", "(Ljava/lang/String;)V");
     jGetDpi = (jmethodID) jEnv->GetStaticMethodID(jGeckoAppShellClass, "getDpi", "()I");
     jSetFullScreen = (jmethodID) jEnv->GetStaticMethodID(jGeckoAppShellClass, "setFullScreen", "(Z)V");
     jShowInputMethodPicker = (jmethodID) jEnv->GetStaticMethodID(jGeckoAppShellClass, "showInputMethodPicker", "()V");
     jHideProgressDialog = (jmethodID) jEnv->GetStaticMethodID(jGeckoAppShellClass, "hideProgressDialog", "()V");
     jPerformHapticFeedback = (jmethodID) jEnv->GetStaticMethodID(jGeckoAppShellClass, "performHapticFeedback", "(Z)V");
+    jVibrate1 = (jmethodID) jEnv->GetStaticMethodID(jGeckoAppShellClass, "vibrate", "(J)V");
+    jVibrateA = (jmethodID) jEnv->GetStaticMethodID(jGeckoAppShellClass, "vibrate", "([JI)V");
+    jCancelVibrate = (jmethodID) jEnv->GetStaticMethodID(jGeckoAppShellClass, "cancelVibrate", "()V");
     jSetKeepScreenOn = (jmethodID) jEnv->GetStaticMethodID(jGeckoAppShellClass, "setKeepScreenOn", "(Z)V");
     jIsNetworkLinkUp = (jmethodID) jEnv->GetStaticMethodID(jGeckoAppShellClass, "isNetworkLinkUp", "()Z");
     jIsNetworkLinkKnown = (jmethodID) jEnv->GetStaticMethodID(jGeckoAppShellClass, "isNetworkLinkKnown", "()Z");
     jSetSelectedLocale = (jmethodID) jEnv->GetStaticMethodID(jGeckoAppShellClass, "setSelectedLocale", "(Ljava/lang/String;)V");
     jScanMedia = (jmethodID) jEnv->GetStaticMethodID(jGeckoAppShellClass, "scanMedia", "(Ljava/lang/String;Ljava/lang/String;)V");
     jGetSystemColors = (jmethodID) jEnv->GetStaticMethodID(jGeckoAppShellClass, "getSystemColors", "()[I");
     jGetIconForExtension = (jmethodID) jEnv->GetStaticMethodID(jGeckoAppShellClass, "getIconForExtension", "(Ljava/lang/String;I)[B");
     jFireAndWaitForTracerEvent = (jmethodID) jEnv->GetStaticMethodID(jGeckoAppShellClass, "fireAndWaitForTracerEvent", "()V");   
@@ -431,19 +434,19 @@ AndroidBridge::GetHandlersForMimeType(co
     getHandlersFromStringArray(mJNIEnv, arr, len, aHandlersArray, 
                                aDefaultApp, aAction,
                                nsDependentCString(aMimeType));
     return true;
 }
 
 bool
 AndroidBridge::GetHandlersForURL(const char *aURL,
-                                      nsIMutableArray* aHandlersArray,
-                                      nsIHandlerApp **aDefaultApp,
-                                      const nsAString& aAction)
+                                 nsIMutableArray* aHandlersArray,
+                                 nsIHandlerApp **aDefaultApp,
+                                 const nsAString& aAction)
 {
     ALOG_BRIDGE("AndroidBridge::GetHandlersForURL");
 
     AutoLocalJNIFrame jniFrame;
     NS_ConvertUTF8toUTF16 wScheme(aURL);
     jstring jstrScheme = mJNIEnv->NewString(wScheme.get(), wScheme.Length());
     jstring jstrAction = mJNIEnv->NewString(nsPromiseFlatString(aAction).get(),
                                             aAction.Length());
@@ -671,16 +674,72 @@ AndroidBridge::HideProgressDialogOnce()
 void
 AndroidBridge::PerformHapticFeedback(bool aIsLongPress)
 {
     ALOG_BRIDGE("AndroidBridge::PerformHapticFeedback");
     mJNIEnv->CallStaticVoidMethod(mGeckoAppShellClass,
                                     jPerformHapticFeedback, aIsLongPress);
 }
 
+void
+AndroidBridge::Vibrate(const nsTArray<PRUint32>& aPattern)
+{
+    ALOG_BRIDGE("AndroidBridge::Vibrate");
+
+    PRUint32 len = aPattern.Length();
+    if (!len) {
+        ALOG_BRIDGE("  invalid 0-length array");
+        return;
+    }
+
+    // It's clear if this worth special-casing, but it creates less
+    // java junk, so dodges the GC.
+    if (len == 1) {
+        jlong d = aPattern[0];
+        if (d < 0) {
+            ALOG_BRIDGE("  invalid vibration duration < 0");
+            return;
+        }
+        mJNIEnv->CallStaticVoidMethod(mGeckoAppShellClass, jVibrate1, d);
+       return;
+    }
+
+    // First element of the array vibrate() expects is how long to wait
+    // *before* vibrating.  For us, this is always 0.
+
+    jlongArray array = mJNIEnv->NewLongArray(len + 1);
+    if (!array) {
+        ALOG_BRIDGE("  failed to allocate array");
+        return;
+    }
+
+    jlong* elts = mJNIEnv->GetLongArrayElements(array, nsnull);
+    elts[0] = 0;
+    for (PRUint32 i = 0; i < aPattern.Length(); ++i) {
+        jlong d = aPattern[i];
+        if (d < 0) {
+            ALOG_BRIDGE("  invalid vibration duration < 0");
+            mJNIEnv->ReleaseLongArrayElements(array, elts, JNI_ABORT);
+            return;
+        }
+        elts[i + 1] = d;
+    }
+    mJNIEnv->ReleaseLongArrayElements(array, elts, 0);
+
+    mJNIEnv->CallStaticVoidMethod(mGeckoAppShellClass, jVibrateA,
+                                  array, -1/*don't repeat*/);
+    // GC owns |array| now?
+}
+
+void
+AndroidBridge::CancelVibrate()
+{
+    mJNIEnv->CallStaticVoidMethod(mGeckoAppShellClass, jCancelVibrate);
+}
+
 bool
 AndroidBridge::IsNetworkLinkUp()
 {
     ALOG_BRIDGE("AndroidBridge::IsNetworkLinkUp");
     return !!mJNIEnv->CallStaticBooleanMethod(mGeckoAppShellClass, jIsNetworkLinkUp);
 }
 
 bool
--- a/widget/src/android/AndroidBridge.h
+++ b/widget/src/android/AndroidBridge.h
@@ -194,16 +194,19 @@ public:
     void AlertsProgressListener_OnCancel(const nsAString& aAlertName);
 
     int GetDPI();
 
     void ShowFilePicker(nsAString& aFilePath, nsAString& aFilters);
 
     void PerformHapticFeedback(bool aIsLongPress);
 
+    void Vibrate(const nsTArray<PRUint32>& aPattern);
+    void CancelVibrate();
+
     void SetFullScreen(bool aFullScreen);
 
     void ShowInputMethodPicker();
 
     void HideProgressDialogOnce();
 
     bool IsNetworkLinkUp();
 
@@ -350,16 +353,19 @@ protected:
     jmethodID jShowFilePicker;
     jmethodID jAlertsProgressListener_OnProgress;
     jmethodID jAlertsProgressListener_OnCancel;
     jmethodID jGetDpi;
     jmethodID jSetFullScreen;
     jmethodID jShowInputMethodPicker;
     jmethodID jHideProgressDialog;
     jmethodID jPerformHapticFeedback;
+    jmethodID jVibrate1;
+    jmethodID jVibrateA;
+    jmethodID jCancelVibrate;
     jmethodID jSetKeepScreenOn;
     jmethodID jIsNetworkLinkUp;
     jmethodID jIsNetworkLinkKnown;
     jmethodID jSetSelectedLocale;
     jmethodID jScanMedia;
     jmethodID jGetSystemColors;
     jmethodID jGetIconForExtension;
     jmethodID jFireAndWaitForTracerEvent;