Bug 779145 - Add support for unsoliciated onShutter and onClosed callbacks, handle OnNavigation to clean-up. r=jst
authorMike Habicher <mikeh@mozilla.com>
Sun, 30 Sep 2012 20:37:47 -0400
changeset 115006 5245844b645d4aa943e5e3ea9accd4c4345309df
parent 115001 31ae286fff781d9cedf951358c19750543740bc7
child 115007 02f3213b154fa50ca1b0b3082ae51005a774cef8
push id1708
push userakeybl@mozilla.com
push dateMon, 19 Nov 2012 21:10:21 +0000
treeherdermozilla-beta@27b14fe50103 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjst
bugs779145
milestone18.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 779145 - Add support for unsoliciated onShutter and onClosed callbacks, handle OnNavigation to clean-up. r=jst
dom/camera/CameraCommon.h
dom/camera/CameraControlImpl.cpp
dom/camera/CameraControlImpl.h
dom/camera/DOMCameraControl.cpp
dom/camera/DOMCameraControl.h
dom/camera/DOMCameraManager.cpp
dom/camera/DOMCameraManager.h
dom/camera/FallbackCameraControl.cpp
dom/camera/GonkCameraControl.cpp
dom/camera/GonkCameraControl.h
dom/camera/GonkCameraHwMgr.cpp
dom/camera/ICameraControl.h
dom/camera/nsIDOMCameraManager.idl
--- a/dom/camera/CameraCommon.h
+++ b/dom/camera/CameraCommon.h
@@ -91,39 +91,16 @@ enum {
   CAMERA_PARAM_SUPPORTED_MAXMETERINGAREAS,
   CAMERA_PARAM_SUPPORTED_MINEXPOSURECOMPENSATION,
   CAMERA_PARAM_SUPPORTED_MAXEXPOSURECOMPENSATION,
   CAMERA_PARAM_SUPPORTED_EXPOSURECOMPENSATIONSTEP,
   CAMERA_PARAM_SUPPORTED_ZOOM,
   CAMERA_PARAM_SUPPORTED_ZOOMRATIOS
 };
 
-class CameraErrorResult : public nsRunnable
-{
-public:
-  CameraErrorResult(nsICameraErrorCallback* onError, const nsString& aErrorMsg)
-    : mOnErrorCb(onError)
-    , mErrorMsg(aErrorMsg)
-  { }
-
-  NS_IMETHOD Run()
-  {
-    MOZ_ASSERT(NS_IsMainThread());
-
-    if (mOnErrorCb) {
-      mOnErrorCb->HandleEvent(mErrorMsg);
-    }
-    return NS_OK;
-  }
-
-protected:
-  nsCOMPtr<nsICameraErrorCallback> mOnErrorCb;
-  const nsString mErrorMsg;
-};
-
 #ifdef PR_LOGGING
 
 static inline void nsLogAddRefCamera(const char *file, uint32_t line, void* p, uint32_t count, const char *clazz, uint32_t size)
 {
   if (count == 1) {
     DOM_CAMERA_LOGR("++++++++++++++++++++++++++++++++++++++++");
   }
   DOM_CAMERA_LOGR("%s:%d : CAMREF-ADD(%s): this=%p, mRefCnt=%d\n", file, line, clazz, p, count);
--- a/dom/camera/CameraControlImpl.cpp
+++ b/dom/camera/CameraControlImpl.cpp
@@ -163,16 +163,96 @@ CameraControlImpl::Get(JSContext* aCx, u
     }
   }
 
   *aValue = JS::ObjectValue(*array);
   return NS_OK;
 }
 
 nsresult
+CameraControlImpl::Set(nsICameraShutterCallback* aOnShutter)
+{
+  mOnShutterCb = aOnShutter;
+  return NS_OK;
+}
+
+nsresult
+CameraControlImpl::Get(nsICameraShutterCallback** aOnShutter)
+{
+  *aOnShutter = mOnShutterCb;
+  return NS_OK;
+}
+
+nsresult
+CameraControlImpl::Set(nsICameraClosedCallback* aOnClosed)
+{
+  mOnClosedCb = aOnClosed;
+  return NS_OK;
+}
+
+nsresult
+CameraControlImpl::Get(nsICameraClosedCallback** aOnClosed)
+{
+  *aOnClosed = mOnClosedCb;
+  return NS_OK;
+}
+
+void
+CameraControlImpl::Shutdown()
+{
+  DOM_CAMERA_LOGT("%s:%d\n", __func__, __LINE__);
+  mAutoFocusOnSuccessCb = nullptr;
+  mAutoFocusOnErrorCb = nullptr;
+  mTakePictureOnSuccessCb = nullptr;
+  mTakePictureOnErrorCb = nullptr;
+  mStartRecordingOnSuccessCb = nullptr;
+  mStartRecordingOnErrorCb = nullptr;
+  mOnShutterCb = nullptr;
+  mOnClosedCb = nullptr;
+}
+
+void
+CameraControlImpl::OnShutterInternal()
+{
+  DOM_CAMERA_LOGI("** SNAP **\n");
+  if (mOnShutterCb) {
+    mOnShutterCb->HandleEvent();
+  }
+}
+
+void
+CameraControlImpl::OnShutter()
+{
+  nsCOMPtr<nsIRunnable> onShutter = NS_NewRunnableMethod(this, &CameraControlImpl::OnShutterInternal);
+  nsresult rv = NS_DispatchToMainThread(onShutter);
+  if (NS_FAILED(rv)) {
+    DOM_CAMERA_LOGW("Failed to dispatch onShutter event to main thread (%d)\n", rv);
+  }
+}
+
+void
+CameraControlImpl::OnClosedInternal()
+{
+  DOM_CAMERA_LOGI("Camera hardware was closed\n");
+  if (mOnClosedCb) {
+    mOnClosedCb->HandleEvent();
+  }
+}
+
+void
+CameraControlImpl::OnClosed()
+{
+  nsCOMPtr<nsIRunnable> onClosed = NS_NewRunnableMethod(this, &CameraControlImpl::OnClosedInternal);
+  nsresult rv = NS_DispatchToMainThread(onClosed);
+  if (NS_FAILED(rv)) {
+    DOM_CAMERA_LOGW("Failed to dispatch onClosed event to main thread (%d)\n", rv);
+  }
+}
+
+nsresult
 CameraControlImpl::GetPreviewStream(CameraSize aSize, nsICameraPreviewStreamCallback* onSuccess, nsICameraErrorCallback* onError)
 {
   /**
    * The camera preview stream object is DOM-facing, and as such
    * must be a cycle-collection participant created on the main
    * thread.
    */
   MOZ_ASSERT(NS_IsMainThread());
@@ -240,14 +320,14 @@ CameraControlImpl::ReceiveFrame(void* aB
   return mDOMPreview->ReceiveFrame(aBuffer, aFormat, aBuilder);
 }
 
 NS_IMETHODIMP
 GetPreviewStreamResult::Run()
 {
   MOZ_ASSERT(NS_IsMainThread());
 
-  if (mOnSuccessCb) {
+  if (mOnSuccessCb && nsDOMCameraManager::IsWindowStillActive(mWindowId)) {
     nsCOMPtr<nsIDOMMediaStream> stream = new DOMCameraPreview(mCameraControl, mWidth, mHeight, mFramesPerSecond);
     mOnSuccessCb->HandleEvent(stream);
   }
   return NS_OK;
 }
--- a/dom/camera/CameraControlImpl.h
+++ b/dom/camera/CameraControlImpl.h
@@ -4,17 +4,17 @@
 
 #ifndef DOM_CAMERA_CAMERACONTROLIMPL_H
 #define DOM_CAMERA_CAMERACONTROLIMPL_H
 
 #include "nsCOMPtr.h"
 #include "nsDOMFile.h"
 #include "DictionaryHelpers.h"
 #include "nsIDOMDeviceStorage.h"
-#include "nsIDOMCameraManager.h"
+#include "DOMCameraManager.h"
 #include "ICameraControl.h"
 #include "CameraCommon.h"
 
 namespace mozilla {
 
 using namespace dom;
 
 class GetPreviewStreamTask;
@@ -39,30 +39,32 @@ class CameraControlImpl : public ICamera
   friend class TakePictureTask;
   friend class StartRecordingTask;
   friend class StopRecordingTask;
   friend class SetParameterTask;
   friend class GetParameterTask;
   friend class GetPreviewStreamVideoModeTask;
 
 public:
-  CameraControlImpl(uint32_t aCameraId, nsIThread* aCameraThread)
+  CameraControlImpl(uint32_t aCameraId, nsIThread* aCameraThread, uint64_t aWindowId)
     : mCameraId(aCameraId)
     , mCameraThread(aCameraThread)
+    , mWindowId(aWindowId)
     , mFileFormat()
     , mMaxMeteringAreas(0)
     , mMaxFocusAreas(0)
     , mDOMPreview(nullptr)
     , mAutoFocusOnSuccessCb(nullptr)
     , mAutoFocusOnErrorCb(nullptr)
     , mTakePictureOnSuccessCb(nullptr)
     , mTakePictureOnErrorCb(nullptr)
     , mStartRecordingOnSuccessCb(nullptr)
     , mStartRecordingOnErrorCb(nullptr)
     , mOnShutterCb(nullptr)
+    , mOnClosedCb(nullptr)
   {
     DOM_CAMERA_LOGT("%s:%d : this=%p\n", __func__, __LINE__, this);
   }
 
   nsresult GetPreviewStream(CameraSize aSize, nsICameraPreviewStreamCallback* onSuccess, nsICameraErrorCallback* onError);
   nsresult StartPreview(DOMCameraPreview* aDOMPreview);
   void StopPreview();
   nsresult AutoFocus(nsICameraAutoFocusCallback* onSuccess, nsICameraErrorCallback* onError);
@@ -72,16 +74,20 @@ public:
   nsresult GetPreviewStreamVideoMode(CameraRecordingOptions* aOptions, nsICameraPreviewStreamCallback* onSuccess, nsICameraErrorCallback* onError);
 
   nsresult Set(uint32_t aKey, const nsAString& aValue);
   nsresult Get(uint32_t aKey, nsAString& aValue);
   nsresult Set(uint32_t aKey, double aValue);
   nsresult Get(uint32_t aKey, double* aValue);
   nsresult Set(JSContext* aCx, uint32_t aKey, const JS::Value& aValue, uint32_t aLimit);
   nsresult Get(JSContext* aCx, uint32_t aKey, JS::Value* aValue);
+  nsresult Set(nsICameraShutterCallback* aOnShutter);
+  nsresult Get(nsICameraShutterCallback** aOnShutter);
+  nsresult Set(nsICameraClosedCallback* aOnClosed);
+  nsresult Get(nsICameraClosedCallback** aOnClosed);
 
   nsresult SetFocusAreas(JSContext* aCx, const JS::Value& aValue)
   {
     return Set(aCx, CAMERA_PARAM_FOCUSAREAS, aValue, mMaxFocusAreas);
   }
 
   nsresult SetMeteringAreas(JSContext* aCx, const JS::Value& aValue)
   {
@@ -92,18 +98,26 @@ public:
   virtual const char* GetParameterConstChar(uint32_t aKey) = 0;
   virtual double GetParameterDouble(uint32_t aKey) = 0;
   virtual void GetParameter(uint32_t aKey, nsTArray<CameraRegion>& aRegions) = 0;
   virtual void SetParameter(const char* aKey, const char* aValue) = 0;
   virtual void SetParameter(uint32_t aKey, const char* aValue) = 0;
   virtual void SetParameter(uint32_t aKey, double aValue) = 0;
   virtual void SetParameter(uint32_t aKey, const nsTArray<CameraRegion>& aRegions) = 0;
   virtual nsresult PushParameters() = 0;
+  virtual void Shutdown();
 
   bool ReceiveFrame(void* aBuffer, ImageFormat aFormat, FrameBuilder aBuilder);
+  void OnShutter();
+  void OnClosed();
+
+  uint64_t GetWindowId()
+  {
+    return mWindowId;
+  }
 
 protected:
   virtual ~CameraControlImpl()
   {
     DOM_CAMERA_LOGT("%s:%d : this=%p\n", __func__, __LINE__, this);
   }
 
   virtual nsresult GetPreviewStreamImpl(GetPreviewStreamTask* aGetPreviewStream) = 0;
@@ -112,18 +126,22 @@ protected:
   virtual nsresult AutoFocusImpl(AutoFocusTask* aAutoFocus) = 0;
   virtual nsresult TakePictureImpl(TakePictureTask* aTakePicture) = 0;
   virtual nsresult StartRecordingImpl(StartRecordingTask* aStartRecording) = 0;
   virtual nsresult StopRecordingImpl(StopRecordingTask* aStopRecording) = 0;
   virtual nsresult PushParametersImpl() = 0;
   virtual nsresult PullParametersImpl() = 0;
   virtual nsresult GetPreviewStreamVideoModeImpl(GetPreviewStreamVideoModeTask* aGetPreviewStreamVideoMode) = 0;
 
+  void OnShutterInternal();
+  void OnClosedInternal();
+
   uint32_t            mCameraId;
   nsCOMPtr<nsIThread> mCameraThread;
+  uint64_t            mWindowId;
   nsString            mFileFormat;
   uint32_t            mMaxMeteringAreas;
   uint32_t            mMaxFocusAreas;
 
   /**
    * 'mDOMPreview' is a raw pointer to the object that will receive incoming
    * preview frames.  This is guaranteed to be valid, or null.
    *
@@ -135,32 +153,60 @@ protected:
 
   nsCOMPtr<nsICameraAutoFocusCallback>      mAutoFocusOnSuccessCb;
   nsCOMPtr<nsICameraErrorCallback>          mAutoFocusOnErrorCb;
   nsCOMPtr<nsICameraTakePictureCallback>    mTakePictureOnSuccessCb;
   nsCOMPtr<nsICameraErrorCallback>          mTakePictureOnErrorCb;
   nsCOMPtr<nsICameraStartRecordingCallback> mStartRecordingOnSuccessCb;
   nsCOMPtr<nsICameraErrorCallback>          mStartRecordingOnErrorCb;
   nsCOMPtr<nsICameraShutterCallback>        mOnShutterCb;
+  nsCOMPtr<nsICameraClosedCallback>         mOnClosedCb;
 
 private:
   CameraControlImpl(const CameraControlImpl&) MOZ_DELETE;
   CameraControlImpl& operator=(const CameraControlImpl&) MOZ_DELETE;
 };
 
+// Error result runnable
+class CameraErrorResult : public nsRunnable
+{
+public:
+  CameraErrorResult(nsICameraErrorCallback* onError, const nsString& aErrorMsg, uint64_t aWindowId)
+    : mOnErrorCb(onError)
+    , mErrorMsg(aErrorMsg)
+    , mWindowId(aWindowId)
+  { }
+
+  NS_IMETHOD Run()
+  {
+    MOZ_ASSERT(NS_IsMainThread());
+
+    if (mOnErrorCb && nsDOMCameraManager::IsWindowStillActive(mWindowId)) {
+      mOnErrorCb->HandleEvent(mErrorMsg);
+    }
+    return NS_OK;
+  }
+
+protected:
+  nsCOMPtr<nsICameraErrorCallback> mOnErrorCb;
+  const nsString mErrorMsg;
+  uint64_t mWindowId;
+};
+
 // Return the resulting preview stream to JS.  Runs on the main thread.
 class GetPreviewStreamResult : public nsRunnable
 {
 public:
-  GetPreviewStreamResult(CameraControlImpl* aCameraControl, uint32_t aWidth, uint32_t aHeight, uint32_t aFramesPerSecond, nsICameraPreviewStreamCallback* onSuccess)
+  GetPreviewStreamResult(CameraControlImpl* aCameraControl, uint32_t aWidth, uint32_t aHeight, uint32_t aFramesPerSecond, nsICameraPreviewStreamCallback* onSuccess, uint64_t aWindowId)
     : mCameraControl(aCameraControl)
     , mWidth(aWidth)
     , mHeight(aHeight)
     , mFramesPerSecond(aFramesPerSecond)
     , mOnSuccessCb(onSuccess)
+    , mWindowId(aWindowId)
   {
     DOM_CAMERA_LOGT("%s:%d : this=%p\n", __func__, __LINE__, this);
   }
 
   virtual ~GetPreviewStreamResult()
   {
     DOM_CAMERA_LOGT("%s:%d : this=%p\n", __func__, __LINE__, this);
   }
@@ -169,16 +215,17 @@ public:
   NS_IMETHOD Run();
 
 protected:
   nsRefPtr<CameraControlImpl> mCameraControl;
   uint32_t mWidth;
   uint32_t mHeight;
   uint32_t mFramesPerSecond;
   nsCOMPtr<nsICameraPreviewStreamCallback> mOnSuccessCb;
+  uint64_t mWindowId;
 };
 
 // Get the desired preview stream.
 class GetPreviewStreamTask : public nsRunnable
 {
 public:
   GetPreviewStreamTask(CameraControlImpl* aCameraControl, CameraSize aSize, nsICameraPreviewStreamCallback* onSuccess, nsICameraErrorCallback* onError)
     : mSize(aSize)
@@ -194,52 +241,54 @@ public:
     DOM_CAMERA_LOGT("%s:%d : this=%p\n", __func__, __LINE__, this);
   }
 
   NS_IMETHOD Run()
   {
     nsresult rv = mCameraControl->GetPreviewStreamImpl(this);
 
     if (NS_FAILED(rv) && mOnErrorCb) {
-      rv = NS_DispatchToMainThread(new CameraErrorResult(mOnErrorCb, NS_LITERAL_STRING("FAILURE")));
+      rv = NS_DispatchToMainThread(new CameraErrorResult(mOnErrorCb, NS_LITERAL_STRING("FAILURE"), mCameraControl->GetWindowId()));
       NS_ENSURE_SUCCESS(rv, rv);
     }
     return rv;
   }
 
   CameraSize mSize;
   nsRefPtr<CameraControlImpl> mCameraControl;
   nsCOMPtr<nsICameraPreviewStreamCallback> mOnSuccessCb;
   nsCOMPtr<nsICameraErrorCallback> mOnErrorCb;
 };
 
 // Return the autofocus status to JS.  Runs on the main thread.
 class AutoFocusResult : public nsRunnable
 {
 public:
-  AutoFocusResult(bool aSuccess, nsICameraAutoFocusCallback* onSuccess)
+  AutoFocusResult(bool aSuccess, nsICameraAutoFocusCallback* onSuccess, uint64_t aWindowId)
     : mSuccess(aSuccess)
     , mOnSuccessCb(onSuccess)
+    , mWindowId(aWindowId)
   { }
 
   virtual ~AutoFocusResult() { }
 
   NS_IMETHOD Run()
   {
     MOZ_ASSERT(NS_IsMainThread());
 
-    if (mOnSuccessCb) {
+    if (mOnSuccessCb && nsDOMCameraManager::IsWindowStillActive(mWindowId)) {
       mOnSuccessCb->HandleEvent(mSuccess);
     }
     return NS_OK;
   }
 
 protected:
   bool mSuccess;
   nsCOMPtr<nsICameraAutoFocusCallback> mOnSuccessCb;
+  uint64_t mWindowId;
 };
 
 // Autofocus the camera.
 class AutoFocusTask : public nsRunnable
 {
 public:
   AutoFocusTask(CameraControlImpl* aCameraControl, nsICameraAutoFocusCallback* onSuccess, nsICameraErrorCallback* onError)
     : mCameraControl(aCameraControl)
@@ -256,58 +305,60 @@ public:
 
   NS_IMETHOD Run()
   {
     DOM_CAMERA_LOGT("%s:%d\n", __func__, __LINE__);
     nsresult rv = mCameraControl->AutoFocusImpl(this);
     DOM_CAMERA_LOGT("%s:%d\n", __func__, __LINE__);
 
     if (NS_FAILED(rv) && mOnErrorCb) {
-      rv = NS_DispatchToMainThread(new CameraErrorResult(mOnErrorCb, NS_LITERAL_STRING("FAILURE")));
+      rv = NS_DispatchToMainThread(new CameraErrorResult(mOnErrorCb, NS_LITERAL_STRING("FAILURE"), mCameraControl->GetWindowId()));
       NS_ENSURE_SUCCESS(rv, rv);
     }
     return rv;
   }
 
   nsRefPtr<CameraControlImpl> mCameraControl;
   nsCOMPtr<nsICameraAutoFocusCallback> mOnSuccessCb;
   nsCOMPtr<nsICameraErrorCallback> mOnErrorCb;
 };
 
 // Return the captured picture to JS.  Runs on the main thread.
 class TakePictureResult : public nsRunnable
 {
 public:
-  TakePictureResult(nsIDOMBlob* aImage, nsICameraTakePictureCallback* onSuccess)
+  TakePictureResult(nsIDOMBlob* aImage, nsICameraTakePictureCallback* onSuccess, uint64_t aWindowId)
     : mImage(aImage)
     , mOnSuccessCb(onSuccess)
+    , mWindowId(aWindowId)
   {
     DOM_CAMERA_LOGT("%s:%d : this=%p\n", __func__, __LINE__, this);
   }
 
   virtual ~TakePictureResult()
   {
     DOM_CAMERA_LOGT("%s:%d : this=%p\n", __func__, __LINE__, this);
   }
 
   NS_IMETHOD Run()
   {
     MOZ_ASSERT(NS_IsMainThread());
 
     DOM_CAMERA_LOGT("%s:%d : this=%p\n", __func__, __LINE__, this);
-    if (mOnSuccessCb) {
+    if (mOnSuccessCb && nsDOMCameraManager::IsWindowStillActive(mWindowId)) {
       mOnSuccessCb->HandleEvent(mImage);
     }
     DOM_CAMERA_LOGT("%s:%d : this=%p\n", __func__, __LINE__, this);
     return NS_OK;
   }
 
 protected:
   nsCOMPtr<nsIDOMBlob> mImage;
   nsCOMPtr<nsICameraTakePictureCallback> mOnSuccessCb;
+  uint64_t mWindowId;
 };
 
 // Capture a still image with the camera.
 class TakePictureTask : public nsRunnable
 {
 public:
   TakePictureTask(CameraControlImpl* aCameraControl, CameraSize aSize, int32_t aRotation, const nsAString& aFileFormat, CameraPosition aPosition, nsICameraTakePictureCallback* onSuccess, nsICameraErrorCallback* onError)
     : mCameraControl(aCameraControl)
@@ -328,17 +379,17 @@ public:
 
   NS_IMETHOD Run()
   {
     DOM_CAMERA_LOGT("%s:%d\n", __func__, __LINE__);
     nsresult rv = mCameraControl->TakePictureImpl(this);
     DOM_CAMERA_LOGT("%s:%d\n", __func__, __LINE__);
 
     if (NS_FAILED(rv) && mOnErrorCb) {
-      rv = NS_DispatchToMainThread(new CameraErrorResult(mOnErrorCb, NS_LITERAL_STRING("FAILURE")));
+      rv = NS_DispatchToMainThread(new CameraErrorResult(mOnErrorCb, NS_LITERAL_STRING("FAILURE"), mCameraControl->GetWindowId()));
       NS_ENSURE_SUCCESS(rv, rv);
     }
     return rv;
   }
 
   nsRefPtr<CameraControlImpl> mCameraControl;
   CameraSize mSize;
   int32_t mRotation;
@@ -347,34 +398,36 @@ public:
   nsCOMPtr<nsICameraTakePictureCallback> mOnSuccessCb;
   nsCOMPtr<nsICameraErrorCallback> mOnErrorCb;
 };
 
 // Return the captured video to JS.  Runs on the main thread.
 class StartRecordingResult : public nsRunnable
 {
 public:
-  StartRecordingResult(nsICameraStartRecordingCallback* onSuccess)
+  StartRecordingResult(nsICameraStartRecordingCallback* onSuccess, uint64_t aWindowId)
     : mOnSuccessCb(onSuccess)
+    , mWindowId(aWindowId)
   { }
 
   virtual ~StartRecordingResult() { }
 
   NS_IMETHOD Run()
   {
     MOZ_ASSERT(NS_IsMainThread());
 
-    if (mOnSuccessCb) {
+    if (mOnSuccessCb && nsDOMCameraManager::IsWindowStillActive(mWindowId)) {
       mOnSuccessCb->HandleEvent();
     }
     return NS_OK;
   }
 
 protected:
   nsCOMPtr<nsICameraStartRecordingCallback> mOnSuccessCb;
+  uint64_t mWindowId;
 };
 
 // Start video recording.
 class StartRecordingTask : public nsRunnable
 {
 public:
   StartRecordingTask(CameraControlImpl* aCameraControl, nsIDOMDeviceStorage* aStorageArea, const nsAString& aFilename, nsICameraStartRecordingCallback* onSuccess, nsICameraErrorCallback* onError)
     : mCameraControl(aCameraControl)
@@ -392,22 +445,18 @@ public:
   }
 
   NS_IMETHOD Run()
   {
     DOM_CAMERA_LOGT("%s:%d\n", __func__, __LINE__);
     nsresult rv = mCameraControl->StartRecordingImpl(this);
     DOM_CAMERA_LOGT("%s:%d : result %d\n", __func__, __LINE__, rv);
 
-    if (NS_SUCCEEDED(rv)) {
-      if (mOnSuccessCb) {
-        rv = NS_DispatchToMainThread(new StartRecordingResult(mOnSuccessCb));
-      }
-    } else if (mOnErrorCb) {
-      rv = NS_DispatchToMainThread(new CameraErrorResult(mOnErrorCb, NS_LITERAL_STRING("FAILURE")));
+    if (NS_FAILED(rv) && mOnErrorCb) {
+      rv = NS_DispatchToMainThread(new CameraErrorResult(mOnErrorCb, NS_LITERAL_STRING("FAILURE"), mCameraControl->GetWindowId()));
     }
     return rv;
   }
 
   nsRefPtr<CameraControlImpl> mCameraControl;
   nsCOMPtr<nsIDOMDeviceStorage> mStorageArea;
   nsString mFilename;
   nsCOMPtr<nsICameraStartRecordingCallback> mOnSuccessCb;
@@ -543,17 +592,17 @@ public:
 
   NS_IMETHOD Run()
   {
     DOM_CAMERA_LOGI("%s:%d -- BEFORE IMPL\n", __func__, __LINE__);
     nsresult rv = mCameraControl->GetPreviewStreamVideoModeImpl(this);
     DOM_CAMERA_LOGI("%s:%d -- AFTER IMPL : rv = %d\n", __func__, __LINE__, rv);
 
     if (NS_FAILED(rv) && mOnErrorCb) {
-      rv = NS_DispatchToMainThread(new CameraErrorResult(mOnErrorCb, NS_LITERAL_STRING("FAILURE")));
+      rv = NS_DispatchToMainThread(new CameraErrorResult(mOnErrorCb, NS_LITERAL_STRING("FAILURE"), mCameraControl->GetWindowId()));
       NS_ENSURE_SUCCESS(rv, rv);
     }
     return NS_OK;
   }
 
   nsRefPtr<CameraControlImpl> mCameraControl;
   CameraRecordingOptions mOptions;
   nsCOMPtr<nsICameraPreviewStreamCallback> mOnSuccessCb;
--- a/dom/camera/DOMCameraControl.cpp
+++ b/dom/camera/DOMCameraControl.cpp
@@ -204,24 +204,34 @@ nsDOMCameraControl::GetExposureCompensat
 {
   return mCameraControl->Get(CAMERA_PARAM_EXPOSURECOMPENSATION, aExposureCompensation);
 }
 
 /* attribute nsICameraShutterCallback onShutter; */
 NS_IMETHODIMP
 nsDOMCameraControl::GetOnShutter(nsICameraShutterCallback** aOnShutter)
 {
-  // TODO: see bug 779138.
-  return NS_ERROR_NOT_IMPLEMENTED;
+  return mCameraControl->Get(aOnShutter);
 }
 NS_IMETHODIMP
 nsDOMCameraControl::SetOnShutter(nsICameraShutterCallback* aOnShutter)
 {
-  // TODO: see bug 779138.
-  return NS_ERROR_NOT_IMPLEMENTED;
+  return mCameraControl->Set(aOnShutter);
+}
+
+/* attribute nsICameraClosedCallback onClosed; */
+NS_IMETHODIMP
+nsDOMCameraControl::GetOnClosed(nsICameraClosedCallback** aOnClosed)
+{
+  return mCameraControl->Get(aOnClosed);
+}
+NS_IMETHODIMP
+nsDOMCameraControl::SetOnClosed(nsICameraClosedCallback* aOnClosed)
+{
+  return mCameraControl->Set(aOnClosed);
 }
 
 /* [implicit_jscontext] void startRecording (in nsIDOMDeviceStorage storageArea, in DOMString filename, in nsICameraStartRecordingCallback onSuccess, [optional] in nsICameraErrorCallback onError); */
 NS_IMETHODIMP
 nsDOMCameraControl::StartRecording(nsIDOMDeviceStorage* storageArea, const nsAString& filename, nsICameraStartRecordingCallback* onSuccess, nsICameraErrorCallback* onError, JSContext* cx)
 {
   NS_ENSURE_TRUE(onSuccess, NS_ERROR_INVALID_ARG);
 
@@ -324,38 +334,41 @@ nsDOMCameraControl::GetPreviewStreamVide
   NS_ENSURE_SUCCESS(rv, rv);
 
   return mCameraControl->GetPreviewStreamVideoMode(&options, onSuccess, onError);
 }
 
 class GetCameraResult : public nsRunnable
 {
 public:
-  GetCameraResult(nsDOMCameraControl* aDOMCameraControl, nsresult aResult, nsICameraGetCameraCallback* onSuccess, nsICameraErrorCallback* onError)
+  GetCameraResult(nsDOMCameraControl* aDOMCameraControl, nsresult aResult, nsICameraGetCameraCallback* onSuccess, nsICameraErrorCallback* onError, uint64_t aWindowId)
     : mDOMCameraControl(aDOMCameraControl)
     , mResult(aResult)
     , mOnSuccessCb(onSuccess)
     , mOnErrorCb(onError)
+    , mWindowId(aWindowId)
   { }
 
   NS_IMETHOD Run()
   {
     MOZ_ASSERT(NS_IsMainThread());
 
-    DOM_CAMERA_LOGT("%s : this=%p -- BEFORE CALLBACK\n", __func__, this);
-    if (NS_FAILED(mResult)) {
-      if (mOnErrorCb) {
-        mOnErrorCb->HandleEvent(NS_LITERAL_STRING("FAILURE"));
+    if (nsDOMCameraManager::IsWindowStillActive(mWindowId)) {
+      DOM_CAMERA_LOGT("%s : this=%p -- BEFORE CALLBACK\n", __func__, this);
+      if (NS_FAILED(mResult)) {
+        if (mOnErrorCb) {
+          mOnErrorCb->HandleEvent(NS_LITERAL_STRING("FAILURE"));
+        }
+      } else {
+        if (mOnSuccessCb) {
+          mOnSuccessCb->HandleEvent(mDOMCameraControl);
+        }
       }
-    } else {
-      if (mOnSuccessCb) {
-        mOnSuccessCb->HandleEvent(mDOMCameraControl);
-      }
+      DOM_CAMERA_LOGT("%s : this=%p -- AFTER CALLBACK\n", __func__, this);
     }
-    DOM_CAMERA_LOGT("%s : this=%p -- AFTER CALLBACK\n", __func__, this);
 
     /**
      * Finally, release the extra reference to the DOM-facing camera control.
      * See the nsDOMCameraControl constructor for the corresponding call to
      * NS_ADDREF_THIS().
      */
     NS_RELEASE(mDOMCameraControl);
     return NS_OK;
@@ -365,16 +378,24 @@ protected:
   /**
    * 'mDOMCameraControl' is a raw pointer to a previously ADDREF()ed object,
    * which is released in Run().
    */
   nsDOMCameraControl* mDOMCameraControl;
   nsresult mResult;
   nsCOMPtr<nsICameraGetCameraCallback> mOnSuccessCb;
   nsCOMPtr<nsICameraErrorCallback> mOnErrorCb;
+  uint64_t mWindowId;
 };
 
 nsresult
-nsDOMCameraControl::Result(nsresult aResult, nsICameraGetCameraCallback* onSuccess, nsICameraErrorCallback* onError)
+nsDOMCameraControl::Result(nsresult aResult, nsICameraGetCameraCallback* onSuccess, nsICameraErrorCallback* onError, uint64_t aWindowId)
 {
-  nsCOMPtr<GetCameraResult> getCameraResult = new GetCameraResult(this, aResult, onSuccess, onError);
+  nsCOMPtr<GetCameraResult> getCameraResult = new GetCameraResult(this, aResult, onSuccess, onError, aWindowId);
   return NS_DispatchToMainThread(getCameraResult);
 }
+
+void
+nsDOMCameraControl::Shutdown()
+{
+  DOM_CAMERA_LOGI("%s:%d\n", __func__, __LINE__);
+  mCameraControl->Shutdown();
+}
--- a/dom/camera/DOMCameraControl.h
+++ b/dom/camera/DOMCameraControl.h
@@ -22,18 +22,20 @@ using namespace dom;
 // Main camera control.
 class nsDOMCameraControl : public nsICameraControl
 {
 public:
   NS_DECL_CYCLE_COLLECTING_ISUPPORTS
   NS_DECL_CYCLE_COLLECTION_CLASS(nsDOMCameraControl)
   NS_DECL_NSICAMERACONTROL
 
-  nsDOMCameraControl(uint32_t aCameraId, nsIThread* aCameraThread, nsICameraGetCameraCallback* onSuccess, nsICameraErrorCallback* onError);
-  nsresult Result(nsresult aResult, nsICameraGetCameraCallback* onSuccess, nsICameraErrorCallback* onError);
+  nsDOMCameraControl(uint32_t aCameraId, nsIThread* aCameraThread, nsICameraGetCameraCallback* onSuccess, nsICameraErrorCallback* onError, uint64_t aWindowId);
+  nsresult Result(nsresult aResult, nsICameraGetCameraCallback* onSuccess, nsICameraErrorCallback* onError, uint64_t aWindowId);
+
+  void Shutdown();
 
 protected:
   virtual ~nsDOMCameraControl();
 
 private:
   nsDOMCameraControl(const nsDOMCameraControl&) MOZ_DELETE;
   nsDOMCameraControl& operator=(const nsDOMCameraControl&) MOZ_DELETE;
 
--- a/dom/camera/DOMCameraManager.cpp
+++ b/dom/camera/DOMCameraManager.cpp
@@ -1,65 +1,79 @@
 /* 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 "nsDebug.h"
-#include "nsIDocument.h"
+#include "nsPIDOMWindow.h"
+#include "mozilla/Services.h"
+#include "nsObserverService.h"
 #include "nsIPermissionManager.h"
 #include "DOMCameraControl.h"
 #include "DOMCameraManager.h"
 #include "nsDOMClassInfo.h"
 #include "DictionaryHelpers.h"
 #include "CameraCommon.h"
 
 using namespace mozilla;
+using namespace dom;
 
 DOMCI_DATA(CameraManager, nsIDOMCameraManager)
 
-NS_INTERFACE_MAP_BEGIN(nsDOMCameraManager)
-  NS_INTERFACE_MAP_ENTRY(nsISupports)
+NS_IMPL_CYCLE_COLLECTION_CLASS(nsDOMCameraManager)
+
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(nsDOMCameraManager)
+  NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR(mCameraThread)
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(nsDOMCameraManager)
+  NS_IMPL_CYCLE_COLLECTION_UNLINK_NSCOMPTR(mCameraThread)
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsDOMCameraManager)
+  NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIObserver)
   NS_INTERFACE_MAP_ENTRY(nsIDOMCameraManager)
+  NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference)
   NS_DOM_INTERFACE_MAP_ENTRY_CLASSINFO(CameraManager)
 NS_INTERFACE_MAP_END
 
-NS_IMPL_ADDREF(nsDOMCameraManager)
-NS_IMPL_RELEASE(nsDOMCameraManager)
+NS_IMPL_CYCLE_COLLECTING_ADDREF(nsDOMCameraManager)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(nsDOMCameraManager)
 
 /**
  * Global camera logging object
  *
  * Set the NSPR_LOG_MODULES environment variable to enable logging
  * in a debug build, e.g. NSPR_LOG_MODULES=Camera:5
  */
 PRLogModuleInfo* gCameraLog = PR_LOG_DEFINE("Camera");
 
 /**
  * nsDOMCameraManager::GetListOfCameras
  * is implementation-specific, and can be found in (e.g.)
  * GonkCameraManager.cpp and FallbackCameraManager.cpp.
  */
 
+WindowTable nsDOMCameraManager::sActiveWindows;
+bool nsDOMCameraManager::sActiveWindowsInitialized = false;
+
 nsDOMCameraManager::nsDOMCameraManager(uint64_t aWindowId)
   : mWindowId(aWindowId)
+  , mCameraThread(nullptr)
 {
   /* member initializers and constructor code */
   DOM_CAMERA_LOGT("%s:%d : this=%p, windowId=%llx\n", __func__, __LINE__, this, mWindowId);
 }
 
 nsDOMCameraManager::~nsDOMCameraManager()
 {
   /* destructor code */
   DOM_CAMERA_LOGT("%s:%d : this=%p\n", __func__, __LINE__, this);
-}
-
-void
-nsDOMCameraManager::OnNavigation(uint64_t aWindowId)
-{
-  // TODO: see bug 779145.
+  nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
+  obs->RemoveObserver(this, "xpcom-shutdown");
 }
 
 // static creator
 already_AddRefed<nsDOMCameraManager>
 nsDOMCameraManager::CheckPermissionAndCreateInstance(nsPIDOMWindow* aWindow)
 {
   nsCOMPtr<nsIPermissionManager> permMgr =
     do_GetService(NS_PERMISSIONMANAGER_CONTRACTID);
@@ -67,18 +81,28 @@ nsDOMCameraManager::CheckPermissionAndCr
 
   uint32_t permission = nsIPermissionManager::DENY_ACTION;
   permMgr->TestPermissionFromWindow(aWindow, "camera", &permission);
   if (permission != nsIPermissionManager::ALLOW_ACTION) {
     NS_WARNING("No permission to access camera");
     return nullptr;
   }
 
+  // Initialize the shared active window tracker
+  if (!sActiveWindowsInitialized) {
+    sActiveWindows.Init();
+    sActiveWindowsInitialized = true;
+  }
+
   nsRefPtr<nsDOMCameraManager> cameraManager =
     new nsDOMCameraManager(aWindow->WindowID());
+
+  nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
+  obs->AddObserver(cameraManager, "xpcom-shutdown", true);
+
   return cameraManager.forget();
 }
 
 /* [implicit_jscontext] void getCamera ([optional] in jsval aOptions, in nsICameraGetCameraCallback onSuccess, [optional] in nsICameraErrorCallback onError); */
 NS_IMETHODIMP
 nsDOMCameraManager::GetCamera(const JS::Value& aOptions, nsICameraGetCameraCallback* onSuccess, nsICameraErrorCallback* onError, JSContext* cx)
 {
   NS_ENSURE_TRUE(onSuccess, NS_ERROR_INVALID_ARG);
@@ -97,12 +121,89 @@ nsDOMCameraManager::GetCamera(const JS::
   if (!mCameraThread) {
     rv = NS_NewThread(getter_AddRefs(mCameraThread));
     NS_ENSURE_SUCCESS(rv, rv);
   }
 
   DOM_CAMERA_LOGT("%s:%d\n", __func__, __LINE__);
 
   // Creating this object will trigger the onSuccess handler
-  nsCOMPtr<nsICameraControl> cameraControl = new nsDOMCameraControl(cameraId, mCameraThread, onSuccess, onError);
+  nsCOMPtr<nsDOMCameraControl> cameraControl = new nsDOMCameraControl(cameraId, mCameraThread, onSuccess, onError, mWindowId);
 
+  Register(cameraControl);
   return NS_OK;
 }
+
+void
+nsDOMCameraManager::Register(nsDOMCameraControl* aDOMCameraControl)
+{
+  DOM_CAMERA_LOGI(">>> Register( aDOMCameraControl = %p ) mWindowId = 0x%llx\n", aDOMCameraControl, mWindowId);
+  MOZ_ASSERT(NS_IsMainThread());
+
+  // Put the camera control into the hash table
+  CameraControls* controls = sActiveWindows.Get(mWindowId);
+  if (!controls) {
+    controls = new CameraControls;
+    sActiveWindows.Put(mWindowId, controls);
+  }
+  controls->AppendElement(aDOMCameraControl);
+}
+
+void
+nsDOMCameraManager::Shutdown(uint64_t aWindowId)
+{
+  DOM_CAMERA_LOGI(">>> Shutdown( aWindowId = 0x%llx )\n", aWindowId);
+  MOZ_ASSERT(NS_IsMainThread());
+
+  CameraControls* controls = sActiveWindows.Get(aWindowId);
+  if (!controls) {
+    return;
+  }
+
+  PRUint32 length = controls->Length();
+  for (PRUint32 i = 0; i < length; i++) {
+    nsRefPtr<nsDOMCameraControl> cameraControl = controls->ElementAt(i);
+    cameraControl->Shutdown();
+  }
+  controls->Clear();
+
+  sActiveWindows.Remove(aWindowId);
+}
+
+void
+nsDOMCameraManager::XpComShutdown()
+{
+  DOM_CAMERA_LOGI(">>> XPCOM Shutdown\n");
+  MOZ_ASSERT(NS_IsMainThread());
+
+  nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
+  obs->RemoveObserver(this, "xpcom-shutdown");
+
+  sActiveWindows.Clear();
+}
+
+nsresult
+nsDOMCameraManager::Observe(nsISupports* aSubject, const char* aTopic, const PRUnichar* aData)
+{
+  if (strcmp(aTopic, "xpcom-shutdown") == 0) {
+    XpComShutdown();
+  }
+  return NS_OK;
+}
+
+void
+nsDOMCameraManager::OnNavigation(uint64_t aWindowId)
+{
+  DOM_CAMERA_LOGI(">>> OnNavigation event\n");
+  Shutdown(aWindowId);
+}
+
+bool
+nsDOMCameraManager::IsWindowStillActive(uint64_t aWindowId)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+
+  if (!sActiveWindowsInitialized) {
+    return false;
+  }
+
+  return !!sActiveWindows.Get(aWindowId);
+}
--- a/dom/camera/DOMCameraManager.h
+++ b/dom/camera/DOMCameraManager.h
@@ -5,43 +5,72 @@
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef DOM_CAMERA_DOMCAMERAMANAGER_H
 #define DOM_CAMERA_DOMCAMERAMANAGER_H
 
 #include "nsCOMPtr.h"
 #include "nsAutoPtr.h"
 #include "nsIThread.h"
+#include "nsIObserver.h"
 #include "nsThreadUtils.h"
+#include "nsHashKeys.h"
+#include "nsWeakReference.h"
+#include "nsClassHashtable.h"
 #include "nsIDOMCameraManager.h"
+#include "nsCycleCollectionParticipant.h"
 #include "mozilla/Attributes.h"
 
 class nsPIDOMWindow;
 
-class nsDOMCameraManager MOZ_FINAL : public nsIDOMCameraManager
+namespace mozilla {
+class nsDOMCameraControl;
+}
+
+typedef nsTArray<nsRefPtr<mozilla::nsDOMCameraControl> > CameraControls;
+typedef nsClassHashtable<nsUint64HashKey, CameraControls> WindowTable;
+
+class nsDOMCameraManager MOZ_FINAL
+  : public nsIDOMCameraManager
+  , public nsIObserver
+  , public nsSupportsWeakReference
 {
 public:
-  NS_DECL_ISUPPORTS
+  NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+  NS_DECL_CYCLE_COLLECTION_CLASS_AMBIGUOUS(nsDOMCameraManager, nsIObserver)
   NS_DECL_NSIDOMCAMERAMANAGER
+  NS_DECL_NSIOBSERVER
 
   static already_AddRefed<nsDOMCameraManager>
     CheckPermissionAndCreateInstance(nsPIDOMWindow* aWindow);
+  static bool IsWindowStillActive(uint64_t aWindowId);
 
+  void Register(mozilla::nsDOMCameraControl* aDOMCameraControl);
   void OnNavigation(uint64_t aWindowId);
 
+protected:
+  void XpComShutdown();
+  void Shutdown(uint64_t aWindowId);
+  ~nsDOMCameraManager();
+
 private:
   nsDOMCameraManager();
   nsDOMCameraManager(uint64_t aWindowId);
   nsDOMCameraManager(const nsDOMCameraManager&) MOZ_DELETE;
   nsDOMCameraManager& operator=(const nsDOMCameraManager&) MOZ_DELETE;
-  ~nsDOMCameraManager();
 
 protected:
   uint64_t mWindowId;
   nsCOMPtr<nsIThread> mCameraThread;
+  /**
+   * 'mActiveWindows' is only ever accessed while in the main thread,
+   * so it is not otherwise protected.
+   */
+  static WindowTable sActiveWindows;
+  static bool sActiveWindowsInitialized;
 };
 
 class GetCameraTask : public nsRunnable
 {
 public:
   GetCameraTask(uint32_t aCameraId, nsICameraGetCameraCallback* onSuccess, nsICameraErrorCallback* onError, nsIThread* aCameraThread)
     : mCameraId(aCameraId)
     , mOnSuccessCb(onSuccess)
--- a/dom/camera/FallbackCameraControl.cpp
+++ b/dom/camera/FallbackCameraControl.cpp
@@ -9,17 +9,17 @@ using namespace mozilla;
 
 /**
  * Fallback camera control subclass.  Can be used as a template for the
  * definition of new camera support classes.
  */
 class nsFallbackCameraControl : public CameraControlImpl
 {
 public:
-  nsFallbackCameraControl(uint32_t aCameraId, nsIThread* aCameraThread, nsDOMCameraControl* aDOMCameraControl, nsICameraGetCameraCallback* onSuccess, nsICameraErrorCallback* onError);
+  nsFallbackCameraControl(uint32_t aCameraId, nsIThread* aCameraThread, nsDOMCameraControl* aDOMCameraControl, nsICameraGetCameraCallback* onSuccess, nsICameraErrorCallback* onError, uint64_t aWindowId);
 
   const char* GetParameter(const char* aKey);
   const char* GetParameterConstChar(uint32_t aKey);
   double GetParameterDouble(uint32_t aKey);
   void GetParameter(uint32_t aKey, nsTArray<dom::CameraRegion>& aRegions);
   void SetParameter(const char* aKey, const char* aValue);
   void SetParameter(uint32_t aKey, const char* aValue);
   void SetParameter(uint32_t aKey, double aValue);
@@ -47,28 +47,28 @@ private:
 /**
  * Stub implementation of the DOM-facing camera control constructor.
  *
  * This should never get called--it exists to keep the linker happy; if
  * implemented, it should construct (e.g.) nsFallbackCameraControl and
  * store a reference in the 'mCameraControl' member (which is why it is
  * defined here).
  */
-nsDOMCameraControl::nsDOMCameraControl(uint32_t aCameraId, nsIThread* aCameraThread, nsICameraGetCameraCallback* onSuccess, nsICameraErrorCallback* onError)
+nsDOMCameraControl::nsDOMCameraControl(uint32_t aCameraId, nsIThread* aCameraThread, nsICameraGetCameraCallback* onSuccess, nsICameraErrorCallback* onError, uint64_t aWindowId)
 {
 }
 
 /**
  * Stub implemetations of the fallback camera control.
  *
  * None of these should ever get called--they exist to keep the linker happy,
  * and may be used as templates for new camera support classes.
  */
-nsFallbackCameraControl::nsFallbackCameraControl(uint32_t aCameraId, nsIThread* aCameraThread, nsDOMCameraControl* aDOMCameraControl, nsICameraGetCameraCallback* onSuccess, nsICameraErrorCallback* onError)
-  : CameraControlImpl(aCameraId, aCameraThread)
+nsFallbackCameraControl::nsFallbackCameraControl(uint32_t aCameraId, nsIThread* aCameraThread, nsDOMCameraControl* aDOMCameraControl, nsICameraGetCameraCallback* onSuccess, nsICameraErrorCallback* onError, uint64_t aWindowId)
+  : CameraControlImpl(aCameraId, aCameraThread, aWindowId)
 {
 }
 
 nsFallbackCameraControl::~nsFallbackCameraControl()
 {
 }
 
 const char*
--- a/dom/camera/GonkCameraControl.cpp
+++ b/dom/camera/GonkCameraControl.cpp
@@ -102,87 +102,89 @@ static const char* getKeyText(uint32_t a
     case CAMERA_PARAM_SUPPORTED_ZOOMRATIOS:
       return CameraParameters::KEY_ZOOM_RATIOS;
     default:
       return nullptr;
   }
 }
 
 // nsDOMCameraControl implementation-specific constructor
-nsDOMCameraControl::nsDOMCameraControl(uint32_t aCameraId, nsIThread* aCameraThread, nsICameraGetCameraCallback* onSuccess, nsICameraErrorCallback* onError)
+nsDOMCameraControl::nsDOMCameraControl(uint32_t aCameraId, nsIThread* aCameraThread, nsICameraGetCameraCallback* onSuccess, nsICameraErrorCallback* onError, uint64_t aWindowId)
   : mDOMCapabilities(nullptr)
 {
   DOM_CAMERA_LOGT("%s:%d : this=%p\n", __func__, __LINE__, this);
 
   /**
    * nsDOMCameraControl is a cycle-collection participant, which means it is
    * not threadsafe--so we need to bump up its reference count here to make
    * sure that it exists long enough to be initialized.
    *
    * Once it is initialized, the GetCameraResult main-thread runnable will
    * decrement it again to make sure it can be cleaned up.
    *
    * nsGonkCameraControl MUST NOT hold a strong reference to this
    * nsDOMCameraControl or memory will leak!
    */
   NS_ADDREF_THIS();
-  mCameraControl = new nsGonkCameraControl(aCameraId, aCameraThread, this, onSuccess, onError);
+  mCameraControl = new nsGonkCameraControl(aCameraId, aCameraThread, this, onSuccess, onError, aWindowId);
 }
 
 // Gonk-specific CameraControl implementation.
 
 // Initialize nsGonkCameraControl instance--runs on camera thread.
 class InitGonkCameraControl : public nsRunnable
 {
 public:
-  InitGonkCameraControl(nsGonkCameraControl* aCameraControl, nsDOMCameraControl* aDOMCameraControl, nsICameraGetCameraCallback* onSuccess, nsICameraErrorCallback* onError)
+  InitGonkCameraControl(nsGonkCameraControl* aCameraControl, nsDOMCameraControl* aDOMCameraControl, nsICameraGetCameraCallback* onSuccess, nsICameraErrorCallback* onError, uint64_t aWindowId)
     : mCameraControl(aCameraControl)
     , mDOMCameraControl(aDOMCameraControl)
     , mOnSuccessCb(onSuccess)
     , mOnErrorCb(onError)
+    , mWindowId(aWindowId)
   {
     DOM_CAMERA_LOGT("%s:%d : this=%p\n", __func__, __LINE__, this);
   }
 
   ~InitGonkCameraControl()
   {
     DOM_CAMERA_LOGT("%s:%d : this=%p\n", __func__, __LINE__, this);
   }
 
   NS_IMETHOD Run()
   {
     nsresult rv = mCameraControl->Init();
-    return mDOMCameraControl->Result(rv, mOnSuccessCb, mOnErrorCb);
+    return mDOMCameraControl->Result(rv, mOnSuccessCb, mOnErrorCb, mWindowId);
   }
 
   nsRefPtr<nsGonkCameraControl> mCameraControl;
   // Raw pointer to DOM-facing camera control--it must NS_ADDREF itself for us
   nsDOMCameraControl* mDOMCameraControl;
   nsCOMPtr<nsICameraGetCameraCallback> mOnSuccessCb;
   nsCOMPtr<nsICameraErrorCallback> mOnErrorCb;
+  uint64_t mWindowId;
 };
 
 // Construct nsGonkCameraControl on the main thread.
-nsGonkCameraControl::nsGonkCameraControl(uint32_t aCameraId, nsIThread* aCameraThread, nsDOMCameraControl* aDOMCameraControl, nsICameraGetCameraCallback* onSuccess, nsICameraErrorCallback* onError)
-  : CameraControlImpl(aCameraId, aCameraThread)
+nsGonkCameraControl::nsGonkCameraControl(uint32_t aCameraId, nsIThread* aCameraThread, nsDOMCameraControl* aDOMCameraControl, nsICameraGetCameraCallback* onSuccess, nsICameraErrorCallback* onError, uint64_t aWindowId)
+  : CameraControlImpl(aCameraId, aCameraThread, aWindowId)
   , mHwHandle(0)
   , mExposureCompensationMin(0.0)
   , mExposureCompensationStep(0.0)
   , mDeferConfigUpdate(false)
   , mWidth(0)
   , mHeight(0)
   , mFormat(PREVIEW_FORMAT_UNKNOWN)
   , mDiscardedFrameCount(0)
 {
   // Constructor runs on the main thread...
   DOM_CAMERA_LOGT("%s:%d : this=%p\n", __func__, __LINE__, this);
   mRwLock = PR_NewRWLock(PR_RWLOCK_RANK_NONE, "GonkCameraControl.Parameters.Lock");
 
   // ...but initialization is carried out on the camera thread.
-  nsCOMPtr<nsIRunnable> init = new InitGonkCameraControl(this, aDOMCameraControl, onSuccess, onError);
+  nsCOMPtr<nsIRunnable> init = new InitGonkCameraControl(this, aDOMCameraControl, onSuccess, onError, aWindowId);
   mCameraThread->Dispatch(init, NS_DISPATCH_NORMAL);
 }
 
 nsresult
 nsGonkCameraControl::Init()
 {
   mHwHandle = GonkCameraHardware::GetHandle(this, mCameraId);
   DOM_CAMERA_LOGI("Initializing camera %d (this=%p, mHwHandle=%d)\n", mCameraId, this, mHwHandle);
@@ -514,17 +516,17 @@ nsGonkCameraControl::SetParameter(uint32
 
 nsresult
 nsGonkCameraControl::GetPreviewStreamImpl(GetPreviewStreamTask* aGetPreviewStream)
 {
   SetPreviewSize(aGetPreviewStream->mSize.width, aGetPreviewStream->mSize.height);
 
   DOM_CAMERA_LOGI("config preview: wated %d x %d, got %d x %d (%d fps, format %d)\n", aGetPreviewStream->mSize.width, aGetPreviewStream->mSize.height, mWidth, mHeight, mFps, mFormat);
 
-  nsCOMPtr<GetPreviewStreamResult> getPreviewStreamResult = new GetPreviewStreamResult(this, mWidth, mHeight, mFps, aGetPreviewStream->mOnSuccessCb);
+  nsCOMPtr<GetPreviewStreamResult> getPreviewStreamResult = new GetPreviewStreamResult(this, mWidth, mHeight, mFps, aGetPreviewStream->mOnSuccessCb, mWindowId);
   return NS_DispatchToMainThread(getPreviewStreamResult);
 }
 
 nsresult
 nsGonkCameraControl::StartPreviewImpl(StartPreviewTask* aStartPreview)
 {
   /**
    * If 'aStartPreview->mDOMPreview' is null, we are just restarting
@@ -582,17 +584,17 @@ nsGonkCameraControl::AutoFocusImpl(AutoF
     /**
      * We already have a callback, so someone has already
      * called autoFocus() -- cancel it.
      */
     mAutoFocusOnSuccessCb = nullptr;
     nsCOMPtr<nsICameraErrorCallback> ecb = mAutoFocusOnErrorCb;
     mAutoFocusOnErrorCb = nullptr;
     if (ecb) {
-      nsresult rv = NS_DispatchToMainThread(new CameraErrorResult(ecb, NS_LITERAL_STRING("CANCELLED")));
+      nsresult rv = NS_DispatchToMainThread(new CameraErrorResult(ecb, NS_LITERAL_STRING("CANCELLED"), mWindowId));
       NS_ENSURE_SUCCESS(rv, rv);
     }
 
     GonkCameraHardware::CancelAutoFocus(mHwHandle);
   }
 
   mAutoFocusOnSuccessCb = aAutoFocus->mOnSuccessCb;
   mAutoFocusOnErrorCb = aAutoFocus->mOnErrorCb;
@@ -611,17 +613,17 @@ nsGonkCameraControl::TakePictureImpl(Tak
     /**
      * We already have a callback, so someone has already
      * called TakePicture() -- cancel it.
      */
     mTakePictureOnSuccessCb = nullptr;
     nsCOMPtr<nsICameraErrorCallback> ecb = mTakePictureOnErrorCb;
     mTakePictureOnErrorCb = nullptr;
     if (ecb) {
-      nsresult rv = NS_DispatchToMainThread(new CameraErrorResult(ecb, NS_LITERAL_STRING("CANCELLED")));
+      nsresult rv = NS_DispatchToMainThread(new CameraErrorResult(ecb, NS_LITERAL_STRING("CANCELLED"), mWindowId));
       NS_ENSURE_SUCCESS(rv, rv);
     }
 
     GonkCameraHardware::CancelTakePicture(mHwHandle);
   }
 
   mTakePictureOnSuccessCb = aTakePicture->mOnSuccessCb;
   mTakePictureOnErrorCb = aTakePicture->mOnErrorCb;
@@ -704,44 +706,27 @@ nsGonkCameraControl::PullParametersImpl(
 
 nsresult
 nsGonkCameraControl::StartRecordingImpl(StartRecordingTask* aStartRecording)
 {
   mStartRecordingOnSuccessCb = aStartRecording->mOnSuccessCb;
   mStartRecordingOnErrorCb = aStartRecording->mOnErrorCb;
 
   /**
-   * We need to pull in the base path from aStartRecording->mStorageArea
-   * once that feature lands.  See bug 795201.
-   *
-   * For now, we just assume /sdcard/Movies.
-   *
-   * Also, the camera app needs to provide the file extension '.3gp' for now.
+   * The camera app needs to provide the file extension '.3gp' for now.
    * See bug 795202.
    */
-#if 1
   nsCOMPtr<nsIFile> filename;
   aStartRecording->mStorageArea->GetRootDirectory(getter_AddRefs(filename));
   filename->Append(aStartRecording->mFilename);
 
   nsAutoCString pathname;
   filename->GetNativePath(pathname);
-#else
-  nsAutoCString pathname(NS_LITERAL_CSTRING("/sdcard/Movies/"));
-  nsAutoCString filename(NS_ConvertUTF16toUTF8(aStartRecording->mFilename));
+  DOM_CAMERA_LOGI("Video pathname is '%s'\n", pathname.get());
 
-  // Make sure that the file name doesn't contain any directory components.
-  if (strcmp(filename.get(), basename(filename.get())) != 0) {
-    DOM_CAMERA_LOGE("Video filename '%s' is not valid\n", filename.get());
-    return NS_ERROR_INVALID_ARG;
-  }
-
-  pathname.Append(filename);
-#endif
-  DOM_CAMERA_LOGI("Video pathname is '%s'\n", pathname.get());
   int fd = open(pathname.get(), O_RDWR | O_CREAT, 0644);
   if (fd < 0) {
     DOM_CAMERA_LOGE("Couldn't create file '%s' with error (%d) %s\n", pathname.get(), errno, strerror(errno));
     return NS_ERROR_FAILURE;
   }
 
   if (SetupRecording(fd) != NS_OK) {
     DOM_CAMERA_LOGE("SetupRecording() failed\n");
@@ -750,17 +735,17 @@ nsGonkCameraControl::StartRecordingImpl(
   }
   if (mRecorder->start() != OK) {
     DOM_CAMERA_LOGE("mRecorder->start() failed\n");
     close(fd);
     return NS_ERROR_FAILURE;
   }
 
   // dispatch the callback
-  nsCOMPtr<nsIRunnable> startRecordingResult = new StartRecordingResult(mStartRecordingOnSuccessCb);
+  nsCOMPtr<nsIRunnable> startRecordingResult = new StartRecordingResult(mStartRecordingOnSuccessCb, mWindowId);
   nsresult rv = NS_DispatchToMainThread(startRecordingResult);
   if (NS_FAILED(rv)) {
     DOM_CAMERA_LOGE("Failed to dispatch start recording result to main thread (%d)!", rv);
   }
   return NS_OK;
 }
 
 nsresult
@@ -783,17 +768,17 @@ nsGonkCameraControl::AutoFocusComplete(b
   PullParametersImpl();
 
   /**
    * If we make it here, regardless of the value of 'aSuccess', we
    * consider the autofocus _process_ to have succeeded.  It is up
    * to the onSuccess callback to determine how to handle the case
    * where the camera wasn't actually able to acquire focus.
    */
-  nsCOMPtr<nsIRunnable> autoFocusResult = new AutoFocusResult(aSuccess, mAutoFocusOnSuccessCb);
+  nsCOMPtr<nsIRunnable> autoFocusResult = new AutoFocusResult(aSuccess, mAutoFocusOnSuccessCb, mWindowId);
   /**
    * Remember to set these to null so that we don't hold any extra
    * references to our document's window.
    */
   mAutoFocusOnSuccessCb = nullptr;
   mAutoFocusOnErrorCb = nullptr;
   nsresult rv = NS_DispatchToMainThread(autoFocusResult);
   if (NS_FAILED(rv)) {
@@ -805,17 +790,17 @@ void
 nsGonkCameraControl::TakePictureComplete(uint8_t* aData, uint32_t aLength)
 {
   uint8_t* data = new uint8_t[aLength];
 
   memcpy(data, aData, aLength);
 
   // TODO: see bug 779144.
   nsIDOMBlob* blob = new nsDOMMemoryFile(static_cast<void*>(data), static_cast<uint64_t>(aLength), NS_LITERAL_STRING("image/jpeg"));
-  nsCOMPtr<nsIRunnable> takePictureResult = new TakePictureResult(blob, mTakePictureOnSuccessCb);
+  nsCOMPtr<nsIRunnable> takePictureResult = new TakePictureResult(blob, mTakePictureOnSuccessCb, mWindowId);
   /**
    * Remember to set these to null so that we don't hold any extra
    * references to our document's window.
    */
   mTakePictureOnSuccessCb = nullptr;
   mTakePictureOnErrorCb = nullptr;
   nsresult rv = NS_DispatchToMainThread(takePictureResult);
   if (NS_FAILED(rv)) {
@@ -1012,17 +997,17 @@ nsGonkCameraControl::GetPreviewStreamVid
   mVideoHeight = aGetPreviewStreamVideoMode->mOptions.height;
   DOM_CAMERA_LOGI("recording preview format: %d x %d (w x h) (rotated %d degrees)\n", mVideoWidth, mVideoHeight, mVideoRotation);
 
   // setup the video mode
   nsresult rv = SetupVideoMode();
   NS_ENSURE_SUCCESS(rv, rv);
 
   // create and return new preview stream object
-  getPreviewStreamResult = new GetPreviewStreamResult(this, mVideoWidth, mVideoHeight, mVideoFrameRate, aGetPreviewStreamVideoMode->mOnSuccessCb);
+  getPreviewStreamResult = new GetPreviewStreamResult(this, mVideoWidth, mVideoHeight, mVideoFrameRate, aGetPreviewStreamVideoMode->mOnSuccessCb, mWindowId);
   rv = NS_DispatchToMainThread(getPreviewStreamResult);
   if (NS_FAILED(rv)) {
     NS_WARNING("Failed to dispatch GetPreviewStreamVideoMode() onSuccess callback to main thread!");
     return rv;
   }
 
   return NS_OK;
 }
@@ -1059,9 +1044,21 @@ GonkFrameBuilder(Image* aImage, void* aB
 void
 ReceiveFrame(nsGonkCameraControl* gc, layers::GraphicBufferLocked* aBuffer)
 {
   if (!gc->ReceiveFrame(aBuffer, ImageFormat::GONK_IO_SURFACE, GonkFrameBuilder)) {
     aBuffer->Unlock();
   }
 }
 
+void
+OnShutter(nsGonkCameraControl* gc)
+{
+  gc->OnShutter();
+}
+
+void
+OnClosed(nsGonkCameraControl* gc)
+{
+  gc->OnClosed();
+}
+
 } // namespace mozilla
--- a/dom/camera/GonkCameraControl.h
+++ b/dom/camera/GonkCameraControl.h
@@ -30,17 +30,17 @@ namespace mozilla {
 
 namespace layers {
 class GraphicBufferLocked;
 }
 
 class nsGonkCameraControl : public CameraControlImpl
 {
 public:
-  nsGonkCameraControl(uint32_t aCameraId, nsIThread* aCameraThread, nsDOMCameraControl* aDOMCameraControl, nsICameraGetCameraCallback* onSuccess, nsICameraErrorCallback* onError);
+  nsGonkCameraControl(uint32_t aCameraId, nsIThread* aCameraThread, nsDOMCameraControl* aDOMCameraControl, nsICameraGetCameraCallback* onSuccess, nsICameraErrorCallback* onError, uint64_t aWindowId);
   nsresult Init();
 
   const char* GetParameter(const char* aKey);
   const char* GetParameterConstChar(uint32_t aKey);
   double GetParameterDouble(uint32_t aKey);
   void GetParameter(uint32_t aKey, nsTArray<dom::CameraRegion>& aRegions);
   void SetParameter(const char* aKey, const char* aValue);
   void SetParameter(uint32_t aKey, const char* aValue);
@@ -115,12 +115,14 @@ private:
   nsGonkCameraControl(const nsGonkCameraControl&) MOZ_DELETE;
   nsGonkCameraControl& operator=(const nsGonkCameraControl&) MOZ_DELETE;
 };
 
 // camera driver callbacks
 void ReceiveImage(nsGonkCameraControl* gc, uint8_t* aData, uint32_t aLength);
 void AutoFocusComplete(nsGonkCameraControl* gc, bool aSuccess);
 void ReceiveFrame(nsGonkCameraControl* gc, layers::GraphicBufferLocked* aBuffer);
+void OnShutter(nsGonkCameraControl* gc);
+void OnClosed(nsGonkCameraControl* gc);
 
 } // namespace mozilla
 
 #endif // DOM_CAMERA_GONKCAMERACONTROL_H
--- a/dom/camera/GonkCameraHwMgr.cpp
+++ b/dom/camera/GonkCameraHwMgr.cpp
@@ -129,17 +129,17 @@ GonkCameraHardware::NotifyCallback(int32
       } else {
         DOM_CAMERA_LOGW("Autofocus failed");
         bSuccess = false;
       }
       AutoFocusComplete(camera, bSuccess);
       break;
 
     case CAMERA_MSG_SHUTTER:
-      DOM_CAMERA_LOGW("Shutter event not handled yet\n");
+      OnShutter(camera);
       break;
 
     default:
       DOM_CAMERA_LOGE("Unhandled notify callback event %d\n", aMsgType);
       break;
   }
 }
 
@@ -193,16 +193,24 @@ GonkCameraHardware::Init()
   mHardware->setCallbacks(GonkCameraHardware::NotifyCallback, GonkCameraHardware::DataCallback, GonkCameraHardware::DataCallbackTimestamp, (void*)sHwHandle);
   mInitialized = true;
 }
 
 GonkCameraHardware::~GonkCameraHardware()
 {
   DOM_CAMERA_LOGT( "%s:%d : this=%p\n", __func__, __LINE__, (void*)this );
   sHw = nullptr;
+
+  /**
+   * Trigger the OnClosed event; the upper layers can't do anything
+   * with the hardware layer once they receive this event.
+   */
+  if (mTarget) {
+    OnClosed(mTarget);
+  }
 }
 
 GonkCameraHardware* GonkCameraHardware::sHw         = nullptr;
 uint32_t            GonkCameraHardware::sHwHandle   = 0;
 
 void
 GonkCameraHardware::ReleaseHandle(uint32_t aHwHandle)
 {
--- a/dom/camera/ICameraControl.h
+++ b/dom/camera/ICameraControl.h
@@ -32,27 +32,33 @@ public:
   virtual nsresult GetPreviewStreamVideoMode(CameraRecordingOptions* aOptions, nsICameraPreviewStreamCallback* onSuccess, nsICameraErrorCallback* onError) = 0;
 
   virtual nsresult Set(uint32_t aKey, const nsAString& aValue) = 0;
   virtual nsresult Get(uint32_t aKey, nsAString& aValue) = 0;
   virtual nsresult Set(uint32_t aKey, double aValue) = 0;
   virtual nsresult Get(uint32_t aKey, double* aValue) = 0;
   virtual nsresult Set(JSContext* aCx, uint32_t aKey, const JS::Value& aValue, uint32_t aLimit) = 0;
   virtual nsresult Get(JSContext* aCx, uint32_t aKey, JS::Value* aValue) = 0;
+  virtual nsresult Set(nsICameraShutterCallback* aOnShutter) = 0;
+  virtual nsresult Get(nsICameraShutterCallback** aOnShutter) = 0;
+  virtual nsresult Set(nsICameraClosedCallback* aOnClosed) = 0;
+  virtual nsresult Get(nsICameraClosedCallback** aOnClosed) = 0;
   virtual nsresult SetFocusAreas(JSContext* aCx, const JS::Value& aValue) = 0;
   virtual nsresult SetMeteringAreas(JSContext* aCx, const JS::Value& aValue) = 0;
 
   virtual const char* GetParameter(const char* aKey) = 0;
   virtual const char* GetParameterConstChar(uint32_t aKey) = 0;
   virtual double GetParameterDouble(uint32_t aKey) = 0;
   virtual void GetParameter(uint32_t aKey, nsTArray<CameraRegion>& aRegions) = 0;
   virtual void SetParameter(const char* aKey, const char* aValue) = 0;
   virtual void SetParameter(uint32_t aKey, const char* aValue) = 0;
   virtual void SetParameter(uint32_t aKey, double aValue) = 0;
   virtual void SetParameter(uint32_t aKey, const nsTArray<CameraRegion>& aRegions) = 0;
 
+  virtual void Shutdown() = 0;
+
 protected:
   virtual ~ICameraControl() { }
 };
 
 } // namespace mozilla
 
 #endif // DOM_CAMERA_ICAMERACONTROL_H
--- a/dom/camera/nsIDOMCameraManager.idl
+++ b/dom/camera/nsIDOMCameraManager.idl
@@ -179,27 +179,33 @@ interface nsICameraStartRecordingCallbac
 };
 
 [scriptable, function, uuid(fb80db71-e315-42f0-9ea9-dd3dd312ed70)]
 interface nsICameraShutterCallback : nsISupports
 {
     void handleEvent();
 };
 
+[scriptable, function, uuid(0ef0f01e-ce74-4741-9bba-54376adfb7a2)]
+interface nsICameraClosedCallback : nsISupports
+{
+    void handleEvent();
+};
+
 [scriptable, function, uuid(a302c6c9-3776-4d1d-a395-f4105d47c3d3)]
 interface nsICameraErrorCallback : nsISupports
 {
     void handleEvent(in DOMString error);
 };
 
 /*
     attributes here affect the preview, any pictures taken, and/or
     any video recorded by the camera.
 */
-[scriptable, uuid(469e0462-59e4-4ed5-afa9-aecd1256ee30)]
+[scriptable, uuid(0f206acd-196b-4bdf-8198-44c1a0cd1998)]
 interface nsICameraControl : nsISupports
 {
     readonly attribute nsICameraCapabilities capabilities;
 
     /* one of the vales chosen from capabilities.effects;
        default is "none" */
     attribute DOMString         effect;
 
@@ -280,16 +286,21 @@ interface nsICameraControl : nsISupports
     [implicit_jscontext]
     void setExposureCompensation([optional] in jsval compensation);
     readonly attribute double   exposureCompensation;
 
     /* the function to call on the camera's shutter event, to trigger
        a shutter sound and/or a visual shutter indicator. */
     attribute nsICameraShutterCallback onShutter;
 
+    /* the function to call when the camera hardware is closed
+       by the underlying framework, e.g. when another app makes a more
+       recent call to get the camera. */
+    attribute nsICameraClosedCallback onClosed;
+
     /* tell the camera to attempt to focus the image */
     void autoFocus(in nsICameraAutoFocusCallback onSuccess, [optional] in nsICameraErrorCallback onError);
 
     /* capture an image and return it as a blob to the 'onSuccess' callback;
        if the camera supports it, this may be invoked while the camera is
        already recording video.
 
        invoking this function will stop the preview stream, which must be