Bug 1104913 - Update selection algorithms for preview, picture and video sizes to have sane defaults. r=mikeh, r=bz
authorAndrew Osmond <aosmond@gmail.com>
Fri, 12 Dec 2014 19:51:29 -0800
changeset 220237 ce9707a48002c8fd80f1c921b13e8f6e40d54f43
parent 220236 3ca34c90002459ca85298e6c50db09fbeb433da1
child 220238 e002b6b44e66ffb159a5a27809b58107e2d5e313
push id27981
push usercbook@mozilla.com
push dateThu, 18 Dec 2014 11:59:05 +0000
treeherdermozilla-central@5c7a6294b82a [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmikeh, bz
bugs1104913
milestone37.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 1104913 - Update selection algorithms for preview, picture and video sizes to have sane defaults. r=mikeh, r=bz
dom/camera/CameraControlImpl.cpp
dom/camera/CameraControlListener.h
dom/camera/DOMCameraControl.cpp
dom/camera/DOMCameraControl.h
dom/camera/DOMCameraControlListener.cpp
dom/camera/GonkCameraControl.cpp
dom/camera/GonkCameraControl.h
dom/camera/ICameraControl.h
dom/camera/test/mochitest.ini
dom/camera/test/test_bug1022766.html
dom/camera/test/test_bug1037322.html
dom/camera/test/test_bug1099390.html
dom/camera/test/test_bug1104913.html
dom/camera/test/test_bug975472.html
dom/camera/test/test_camera.html
dom/camera/test/test_camera_2.html
dom/camera/test/test_camera_3.html
dom/camera/test/test_camera_bad_initial_config.html
dom/camera/test/test_camera_fake_parameters.html
dom/camera/test/test_camera_hardware_auto_focus_moving_cb.html
dom/camera/test/test_camera_hardware_face_detection.html
dom/camera/test/test_camera_hardware_failures.html
dom/camera/test/test_camera_hardware_init_failure.html
dom/webidl/CameraConfigurationEvent.webidl
dom/webidl/CameraControl.webidl
dom/webidl/CameraManager.webidl
--- a/dom/camera/CameraControlImpl.cpp
+++ b/dom/camera/CameraControlImpl.cpp
@@ -15,20 +15,21 @@
 
 using namespace mozilla;
 
 /* static */ StaticRefPtr<nsIThread> CameraControlImpl::sCameraThread;
 
 CameraControlImpl::CameraControlImpl()
   : mListenerLock(PR_NewRWLock(PR_RWLOCK_RANK_NONE, "CameraControlImpl.Listeners.Lock"))
   , mPreviewState(CameraControlListener::kPreviewStopped)
-  , mHardwareState(CameraControlListener::kHardwareClosed)
+  , mHardwareState(CameraControlListener::kHardwareUninitialized)
   , mHardwareStateChangeReason(NS_OK)
 {
   DOM_CAMERA_LOGT("%s:%d : this=%p\n", __func__, __LINE__, this);
+  mCurrentConfiguration.mMode = ICameraControl::kUnspecifiedMode;
 
   // reuse the same camera thread to conserve resources
   nsCOMPtr<nsIThread> ct = do_QueryInterface(sCameraThread);
   if (ct) {
     mCameraThread = ct.forget();
   } else {
     nsresult rv = NS_NewNamedThread("CameraThread", getter_AddRefs(mCameraThread));
     if (NS_FAILED(rv)) {
@@ -74,17 +75,17 @@ CameraControlImpl::OnHardwareStateChange
   RwLockAutoEnterRead lock(mListenerLock);
 
   if (aNewState == mHardwareState) {
     DOM_CAMERA_LOGI("OnHardwareStateChange: state did not change from %d\n", mHardwareState);
     return;
   }
 
 #ifdef PR_LOGGING
-  const char* state[] = { "closed", "open", "failed" };
+  const char* state[] = { "uninitialized", "closed", "open", "failed" };
   MOZ_ASSERT(aNewState >= 0);
   if (static_cast<unsigned int>(aNewState) < sizeof(state) / sizeof(state[0])) {
     DOM_CAMERA_LOGI("New hardware state is '%s' (reason=0x%x)\n",
       state[aNewState], aReason);
   } else {
     DOM_CAMERA_LOGE("OnHardwareStateChange: got invalid HardwareState value %d\n", aNewState);
   }
 #endif
--- a/dom/camera/CameraControlListener.h
+++ b/dom/camera/CameraControlListener.h
@@ -29,16 +29,17 @@ protected:
     MOZ_COUNT_DTOR(CameraControlListener);
   }
 
 public:
   NS_INLINE_DECL_THREADSAFE_REFCOUNTING(CameraControlListener);
 
   enum HardwareState
   {
+    kHardwareUninitialized,
     kHardwareClosed,
     kHardwareOpen,
     kHardwareOpenFailed
   };
   // aReason:
   //    NS_OK : state change was expected and normal;
   //    NS_ERROR_FAILURE : one or more system-level components failed and
   //                       the camera was closed;
--- a/dom/camera/DOMCameraControl.cpp
+++ b/dom/camera/DOMCameraControl.cpp
@@ -197,16 +197,17 @@ nsDOMCameraControl::nsDOMCameraControl(u
                                        Promise* aPromise,
                                        nsPIDOMWindow* aWindow)
   : DOMMediaStream()
   , mCameraControl(nullptr)
   , mAudioChannelAgent(nullptr)
   , mGetCameraPromise(aPromise)
   , mWindow(aWindow)
   , mPreviewState(CameraControlListener::kPreviewStopped)
+  , mSetInitialConfig(false)
 {
   DOM_CAMERA_LOGT("%s:%d : this=%p\n", __func__, __LINE__, this);
   mInput = new CameraPreviewMediaStream(this);
 
   BindToOwner(aWindow);
 
   nsRefPtr<DOMCameraConfiguration> initialConfig =
     new DOMCameraConfiguration(aInitialConfig);
@@ -231,18 +232,24 @@ nsDOMCameraControl::nsDOMCameraControl(u
       break;
 
     default:
       MOZ_ASSERT_UNREACHABLE("Unanticipated camera mode!");
       break;
   }
 
   if (haveInitialConfig) {
-    config.mPreviewSize.width = aInitialConfig.mPreviewSize.mWidth;
-    config.mPreviewSize.height = aInitialConfig.mPreviewSize.mHeight;
+    rv = SelectPreviewSize(aInitialConfig.mPreviewSize, config.mPreviewSize);
+    if (NS_FAILED(rv)) {
+      mListener->OnUserError(DOMCameraControlListener::kInStartCamera, rv);
+      return;
+    }
+
+    config.mPictureSize.width = aInitialConfig.mPictureSize.mWidth;
+    config.mPictureSize.height = aInitialConfig.mPictureSize.mHeight;
     config.mRecorderProfile = aInitialConfig.mRecorderProfile;
   }
 
 #ifdef MOZ_WIDGET_GONK
   bool gotCached = false;
   if (sCachedCameraControl && aCameraId == kDefaultCameraId) {
     mCameraControl = sCachedCameraControl;
     sCachedCameraControl = nullptr;
@@ -269,23 +276,29 @@ nsDOMCameraControl::nsDOMCameraControl(u
   mCameraControl->AddListener(mListener);
 
 #ifdef MOZ_WIDGET_GONK
   if (!gotCached || NS_FAILED(sCachedCameraControlStartResult)) {
 #endif
     // Start the camera...
     if (haveInitialConfig) {
       rv = mCameraControl->Start(&config);
+      if (NS_SUCCEEDED(rv)) {
+        mSetInitialConfig = true;
+      }
     } else {
       rv = mCameraControl->Start();
     }
 #ifdef MOZ_WIDGET_GONK
   } else {
     if (haveInitialConfig) {
       rv = mCameraControl->SetConfiguration(config);
+      if (NS_SUCCEEDED(rv)) {
+        mSetInitialConfig = true;
+      }
     } else {
       rv = NS_OK;
     }
   }
 #endif
   if (NS_FAILED(rv)) {
     mListener->OnUserError(DOMCameraControlListener::kInStartCamera, rv);
   }
@@ -303,16 +316,56 @@ nsDOMCameraControl::WrapObject(JSContext
 }
 
 bool
 nsDOMCameraControl::IsWindowStillActive()
 {
   return nsDOMCameraManager::IsWindowStillActive(mWindow->WindowID());
 }
 
+nsresult
+nsDOMCameraControl::SelectPreviewSize(const CameraSize& aRequestedPreviewSize, ICameraControl::Size& aSelectedPreviewSize)
+{
+  if (aRequestedPreviewSize.mWidth && aRequestedPreviewSize.mHeight) {
+    aSelectedPreviewSize.width = aRequestedPreviewSize.mWidth;
+    aSelectedPreviewSize.height = aRequestedPreviewSize.mHeight;
+  } else {
+    /* Use the window width and height if no preview size is provided.
+       Note that the width and height are actually reversed from the
+       camera perspective. */
+    int32_t width = 0;
+    int32_t height = 0;
+    float ratio = 0.0;
+    nsresult rv;
+
+    rv = mWindow->GetDevicePixelRatio(&ratio);
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      return rv;
+    }
+
+    rv = mWindow->GetInnerWidth(&height);
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      return rv;
+    }
+
+    rv = mWindow->GetInnerHeight(&width);
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      return rv;
+    }
+
+    MOZ_ASSERT(width > 0);
+    MOZ_ASSERT(height > 0);
+    MOZ_ASSERT(ratio > 0.0);
+    aSelectedPreviewSize.width = std::ceil(width * ratio);
+    aSelectedPreviewSize.height = std::ceil(height * ratio);
+  }
+
+  return NS_OK;
+}
+
 // Setter for weighted regions: { top, bottom, left, right, weight }
 nsresult
 nsDOMCameraControl::Set(uint32_t aKey, const Optional<Sequence<CameraRegion> >& aValue, uint32_t aLimit)
 {
   if (aLimit == 0) {
     DOM_CAMERA_LOGI("%s:%d : aLimit = 0, nothing to do\n", __func__, __LINE__);
     return NS_OK;
   }
@@ -796,19 +849,24 @@ nsDOMCameraControl::SetConfiguration(con
 
   if (mTakePicturePromise) {
     // We're busy taking a picture, can't change modes right now.
     promise->MaybeReject(NS_ERROR_IN_PROGRESS);
     return promise.forget();
   }
 
   ICameraControl::Configuration config;
+  aRv = SelectPreviewSize(aConfiguration.mPreviewSize, config.mPreviewSize);
+  if (aRv.Failed()) {
+    return nullptr;
+  }
+
   config.mRecorderProfile = aConfiguration.mRecorderProfile;
-  config.mPreviewSize.width = aConfiguration.mPreviewSize.mWidth;
-  config.mPreviewSize.height = aConfiguration.mPreviewSize.mHeight;
+  config.mPictureSize.width = aConfiguration.mPictureSize.mWidth;
+  config.mPictureSize.height = aConfiguration.mPictureSize.mHeight;
   config.mMode = ICameraControl::kPictureMode;
   if (aConfiguration.mMode == CameraMode::Video) {
     config.mMode = ICameraControl::kVideoMode;
   }
 
   aRv = mCameraControl->SetConfiguration(config);
   if (aRv.Failed()) {
     return nullptr;
@@ -1036,46 +1094,54 @@ nsDOMCameraControl::DispatchStateEvent(c
   eventInit.mNewState = aState;
 
   nsRefPtr<CameraStateChangeEvent> event =
     CameraStateChangeEvent::Constructor(this, aType, eventInit);
 
   DispatchTrustedEvent(event);
 }
 
+void
+nsDOMCameraControl::OnGetCameraComplete()
+{
+  // The hardware is open, so we can return a camera to JS, even if
+  // the preview hasn't started yet.
+  nsRefPtr<Promise> promise = mGetCameraPromise.forget();
+  if (promise) {
+    CameraGetPromiseData data;
+    data.mCamera = this;
+    data.mConfiguration = *mCurrentConfiguration;
+    promise->MaybeResolve(data);
+  }
+}
+
 // Camera Control event handlers--must only be called from the Main Thread!
 void
 nsDOMCameraControl::OnHardwareStateChange(CameraControlListener::HardwareState aState,
                                           nsresult aReason)
 {
   DOM_CAMERA_LOGT("%s:%d : this=%p\n", __func__, __LINE__, this);
   MOZ_ASSERT(NS_IsMainThread());
 
   ErrorResult ignored;
 
   switch (aState) {
     case CameraControlListener::kHardwareOpen:
       DOM_CAMERA_LOGI("DOM OnHardwareStateChange: open\n");
       MOZ_ASSERT(aReason == NS_OK);
-      {
+      if (!mSetInitialConfig) {
         // The hardware is open, so we can return a camera to JS, even if
         // the preview hasn't started yet.
-        nsRefPtr<Promise> promise = mGetCameraPromise.forget();
-        if (promise) {
-          CameraGetPromiseData data;
-          data.mCamera = this;
-          data.mConfiguration = *mCurrentConfiguration;
-          promise->MaybeResolve(data);
-        }
+        OnGetCameraComplete();
       }
       break;
 
     case CameraControlListener::kHardwareClosed:
       DOM_CAMERA_LOGI("DOM OnHardwareStateChange: closed\n");
-      {
+      if (!mSetInitialConfig) {
         nsRefPtr<Promise> promise = mReleasePromise.forget();
         if (promise) {
           promise->MaybeResolve(JS::UndefinedHandleValue);
         }
 
         CameraClosedEventInit eventInit;
         switch (aReason) {
           case NS_OK:
@@ -1097,25 +1163,31 @@ nsDOMCameraControl::OnHardwareStateChang
             break;
         }
 
         nsRefPtr<CameraClosedEvent> event =
           CameraClosedEvent::Constructor(this,
                                          NS_LITERAL_STRING("close"),
                                          eventInit);
         DispatchTrustedEvent(event);
+      } else {
+        // The configuration failed and we forced the camera to shutdown.
+        OnUserError(DOMCameraControlListener::kInStartCamera, NS_ERROR_NOT_AVAILABLE);
       }
       break;
 
     case CameraControlListener::kHardwareOpenFailed:
       DOM_CAMERA_LOGI("DOM OnHardwareStateChange: open failed\n");
       MOZ_ASSERT(aReason == NS_ERROR_NOT_AVAILABLE);
       OnUserError(DOMCameraControlListener::kInStartCamera, NS_ERROR_NOT_AVAILABLE);
       break;
 
+    case CameraControlListener::kHardwareUninitialized:
+      break;
+
     default:
       DOM_CAMERA_LOGE("DOM OnHardwareStateChange: UNKNOWN=%d\n", aState);
       MOZ_ASSERT_UNREACHABLE("Unanticipated camera hardware state");
   }
 }
 
 void
 nsDOMCameraControl::OnShutter()
@@ -1222,30 +1294,41 @@ nsDOMCameraControl::OnConfigurationChang
   DOM_CAMERA_LOGI("    mode                   : %s\n",
     mCurrentConfiguration->mMode == CameraMode::Video ? "video" : "picture");
   DOM_CAMERA_LOGI("    maximum focus areas    : %d\n",
     mCurrentConfiguration->mMaxFocusAreas);
   DOM_CAMERA_LOGI("    maximum metering areas : %d\n",
     mCurrentConfiguration->mMaxMeteringAreas);
   DOM_CAMERA_LOGI("    preview size (w x h)   : %d x %d\n",
     mCurrentConfiguration->mPreviewSize.mWidth, mCurrentConfiguration->mPreviewSize.mHeight);
+  DOM_CAMERA_LOGI("    picture size (w x h)   : %d x %d\n",
+    mCurrentConfiguration->mPictureSize.mWidth, mCurrentConfiguration->mPictureSize.mHeight);
   DOM_CAMERA_LOGI("    recorder profile       : %s\n",
     NS_ConvertUTF16toUTF8(mCurrentConfiguration->mRecorderProfile).get());
 
+  if (mSetInitialConfig) {
+    OnGetCameraComplete();
+    mSetInitialConfig = false;
+    return;
+  }
+
   nsRefPtr<Promise> promise = mSetConfigurationPromise.forget();
   if (promise) {
     promise->MaybeResolve(*aConfiguration);
   }
 
   CameraConfigurationEventInit eventInit;
   eventInit.mMode = mCurrentConfiguration->mMode;
   eventInit.mRecorderProfile = mCurrentConfiguration->mRecorderProfile;
   eventInit.mPreviewSize = new DOMRect(static_cast<DOMMediaStream*>(this), 0, 0,
                                        mCurrentConfiguration->mPreviewSize.mWidth,
                                        mCurrentConfiguration->mPreviewSize.mHeight);
+  eventInit.mPictureSize = new DOMRect(static_cast<DOMMediaStream*>(this), 0, 0,
+                                       mCurrentConfiguration->mPictureSize.mWidth,
+                                       mCurrentConfiguration->mPictureSize.mHeight);
 
   nsRefPtr<CameraConfigurationEvent> event =
     CameraConfigurationEvent::Constructor(this,
                                           NS_LITERAL_STRING("configurationchanged"),
                                           eventInit);
 
   DispatchTrustedEvent(event);
 }
@@ -1354,16 +1437,26 @@ nsDOMCameraControl::OnUserError(CameraCo
           promise->MaybeResolve(JS::UndefinedHandleValue);
         }
 
         return;
       }
       break;
 
     case CameraControlListener::kInSetConfiguration:
+      if (mSetInitialConfig) {
+        // If the SetConfiguration() call in the constructor fails, there
+        // is nothing we can do except release the camera hardware. This
+        // will trigger a hardware state change, and when the flag that
+        // got us here is set in that handler, we replace the normal reason
+        // code with one that indicates the hardware isn't available.
+        DOM_CAMERA_LOGI("Failed to configure cached camera, stopping\n");
+        mCameraControl->Stop();
+        return;
+      }
       promise = mSetConfigurationPromise.forget();
       break;
 
     case CameraControlListener::kInAutoFocus:
       promise = mAutoFocusPromise.forget();
       DispatchStateEvent(NS_LITERAL_STRING("focus"), NS_LITERAL_STRING("unfocused"));
       break;
 
--- a/dom/camera/DOMCameraControl.h
+++ b/dom/camera/DOMCameraControl.h
@@ -166,24 +166,26 @@ protected:
 
   void OnCreatedFileDescriptor(bool aSucceeded);
 
   void OnAutoFocusComplete(bool aAutoFocusSucceeded);
   void OnAutoFocusMoving(bool aIsMoving);
   void OnTakePictureComplete(nsIDOMBlob* aPicture);
   void OnFacesDetected(const nsTArray<ICameraControl::Face>& aFaces);
 
+  void OnGetCameraComplete();
   void OnHardwareStateChange(DOMCameraControlListener::HardwareState aState, nsresult aReason);
   void OnPreviewStateChange(DOMCameraControlListener::PreviewState aState);
   void OnRecorderStateChange(CameraControlListener::RecorderState aState, int32_t aStatus, int32_t aTrackNum);
   void OnConfigurationChange(DOMCameraConfiguration* aConfiguration);
   void OnShutter();
   void OnUserError(CameraControlListener::UserContext aContext, nsresult aError);
 
   bool IsWindowStillActive();
+  nsresult SelectPreviewSize(const dom::CameraSize& aRequestedPreviewSize, ICameraControl::Size& aSelectedPreviewSize);
 
   nsresult NotifyRecordingStatusChange(const nsString& aMsg);
 
   already_AddRefed<dom::Promise> CreatePromise(ErrorResult& aRv);
   void AbortPromise(nsRefPtr<dom::Promise>& aPromise);
   virtual void EventListenerAdded(nsIAtom* aType) MOZ_OVERRIDE;
   void DispatchPreviewStateEvent(DOMCameraControlListener::PreviewState aState);
   void DispatchStateEvent(const nsString& aType, const nsString& aState);
@@ -217,16 +219,18 @@ protected:
 
   // set once when this object is created
   nsCOMPtr<nsPIDOMWindow>   mWindow;
 
   dom::CameraStartRecordingOptions mOptions;
   nsRefPtr<DeviceStorageFileDescriptor> mDSFileDescriptor;
   DOMCameraControlListener::PreviewState mPreviewState;
 
+  bool mSetInitialConfig;
+
 #ifdef MOZ_WIDGET_GONK
   // cached camera control, to improve start-up time
   static StaticRefPtr<ICameraControl> sCachedCameraControl;
   static nsresult sCachedCameraControlStartResult;
   static nsCOMPtr<nsITimer> sDiscardCachedCameraControlTimer;
 #endif
 
 private:
--- a/dom/camera/DOMCameraControlListener.cpp
+++ b/dom/camera/DOMCameraControlListener.cpp
@@ -210,16 +210,18 @@ DOMCameraControlListener::OnConfiguratio
           DOM_CAMERA_LOGI("Camera mode still unspecified, nothing to do\n");
           return;
       }
 
       // Map CameraControl parameters to their DOM-facing equivalents
       config->mRecorderProfile = mConfiguration.mRecorderProfile;
       config->mPreviewSize.mWidth = mConfiguration.mPreviewSize.width;
       config->mPreviewSize.mHeight = mConfiguration.mPreviewSize.height;
+      config->mPictureSize.mWidth = mConfiguration.mPictureSize.width;
+      config->mPictureSize.mHeight = mConfiguration.mPictureSize.height;
       config->mMaxMeteringAreas = mConfiguration.mMaxMeteringAreas;
       config->mMaxFocusAreas = mConfiguration.mMaxFocusAreas;
 
       aDOMCameraControl->OnConfigurationChange(config);
     }
 
   protected:
     const CameraListenerConfiguration mConfiguration;
--- a/dom/camera/GonkCameraControl.cpp
+++ b/dom/camera/GonkCameraControl.cpp
@@ -57,17 +57,16 @@ using namespace android;
       DOM_CAMERA_LOGE("%s:%d : mCameraHw is null\n", __func__, __LINE__); \
       return NS_ERROR_NOT_INITIALIZED;                                    \
     }                                                                     \
   } while(0)
 
 // Construct nsGonkCameraControl on the main thread.
 nsGonkCameraControl::nsGonkCameraControl(uint32_t aCameraId)
   : mCameraId(aCameraId)
-  , mLastPictureSize({0, 0})
   , mLastThumbnailSize({0, 0})
   , mPreviewFps(30)
   , mResumePreviewAfterTakingPicture(false) // XXXmikeh - see bug 950102
   , mFlashSupported(false)
   , mLuminanceSupported(false)
   , mAutoFlashModeOverridden(false)
   , mSeparateVideoAndPreviewSizesSupported(false)
   , mDeferConfigUpdate(0)
@@ -131,20 +130,26 @@ nsGonkCameraControl::StartInternal(const
     if (NS_WARN_IF(NS_FAILED(rv))) {
       // The initial configuration failed, close up the hardware
       StopInternal();
       return rv;
     }
   }
 
   OnHardwareStateChange(CameraControlListener::kHardwareOpen, NS_OK);
+
   if (aInitialConfig) {
-    return StartPreviewImpl();
+    rv = StartPreviewInternal();
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      return rv;
+    }
+
+    OnConfigurationChange();
+    OnPreviewStateChange(CameraControlListener::kPreviewStarted);
   }
-
   return NS_OK;
 }
 
 nsresult
 nsGonkCameraControl::Initialize()
 {
   if (mCameraHw.get()) {
     DOM_CAMERA_LOGI("Camera %d already connected (this=%p)\n", mCameraId, this);
@@ -175,34 +180,34 @@ nsGonkCameraControl::Initialize()
 
   // The emulator's camera returns -1 for these values; bump them up to 0
   int areas;
   mParams.Get(CAMERA_PARAM_SUPPORTED_MAXMETERINGAREAS, areas);
   mCurrentConfiguration.mMaxMeteringAreas = areas != -1 ? areas : 0;
   mParams.Get(CAMERA_PARAM_SUPPORTED_MAXFOCUSAREAS, areas);
   mCurrentConfiguration.mMaxFocusAreas = areas != -1 ? areas : 0;
 
-  mParams.Get(CAMERA_PARAM_PICTURE_SIZE, mLastPictureSize);
+  mParams.Get(CAMERA_PARAM_PICTURE_SIZE, mCurrentConfiguration.mPictureSize);
   mParams.Get(CAMERA_PARAM_PREVIEWSIZE, mCurrentConfiguration.mPreviewSize);
 
   nsString luminance; // check for support
   mParams.Get(CAMERA_PARAM_LUMINANCE, luminance);
   mLuminanceSupported = !luminance.IsEmpty();
 
   nsString flashMode;
   mParams.Get(CAMERA_PARAM_FLASHMODE, flashMode);
   mFlashSupported = !flashMode.IsEmpty();
 
   double quality; // informational only
   mParams.Get(CAMERA_PARAM_PICTURE_QUALITY, quality);
 
   DOM_CAMERA_LOGI(" - maximum metering areas:        %u\n", mCurrentConfiguration.mMaxMeteringAreas);
   DOM_CAMERA_LOGI(" - maximum focus areas:           %u\n", mCurrentConfiguration.mMaxFocusAreas);
   DOM_CAMERA_LOGI(" - default picture size:          %u x %u\n",
-    mLastPictureSize.width, mLastPictureSize.height);
+    mCurrentConfiguration.mPictureSize.width, mCurrentConfiguration.mPictureSize.height);
   DOM_CAMERA_LOGI(" - default picture file format:   %s\n",
     NS_ConvertUTF16toUTF8(mFileFormat).get());
   DOM_CAMERA_LOGI(" - default picture quality:       %f\n", quality);
   DOM_CAMERA_LOGI(" - default thumbnail size:        %u x %u\n",
     mLastThumbnailSize.width, mLastThumbnailSize.height);
   DOM_CAMERA_LOGI(" - default preview size:          %u x %u\n",
     mCurrentConfiguration.mPreviewSize.width, mCurrentConfiguration.mPreviewSize.height);
   DOM_CAMERA_LOGI(" - luminance reporting:           %ssupported\n",
@@ -217,16 +222,21 @@ nsGonkCameraControl::Initialize()
   nsAutoTArray<Size, 16> sizes;
   mParams.Get(CAMERA_PARAM_SUPPORTED_VIDEOSIZES, sizes);
   if (sizes.Length() > 0) {
     mSeparateVideoAndPreviewSizesSupported = true;
     DOM_CAMERA_LOGI(" - support for separate preview and video sizes\n");
     mParams.Get(CAMERA_PARAM_VIDEOSIZE, mLastRecorderSize);
     DOM_CAMERA_LOGI(" - default video recorder size:   %u x %u\n",
       mLastRecorderSize.width, mLastRecorderSize.height);
+
+    Size preferred;
+    mParams.Get(CAMERA_PARAM_PREFERRED_PREVIEWSIZE_FOR_VIDEO, preferred);
+    DOM_CAMERA_LOGI(" - preferred video preview size:  %u x %u\n",
+      preferred.width, preferred.height);
   } else {
     mLastRecorderSize = mCurrentConfiguration.mPreviewSize;
   }
 
   nsAutoTArray<nsString, 8> modes;
   mParams.Get(CAMERA_PARAM_SUPPORTED_METERINGMODES, modes);
   if (!modes.IsEmpty()) {
     nsString mode;
@@ -257,55 +267,97 @@ nsGonkCameraControl::~nsGonkCameraContro
 {
   DOM_CAMERA_LOGT("%s:%d : this=%p, mCameraHw = %p\n", __func__, __LINE__, this, mCameraHw.get());
 
   StopImpl();
   DOM_CAMERA_LOGT("%s:%d\n", __func__, __LINE__);
 }
 
 nsresult
+nsGonkCameraControl::ValidateConfiguration(const Configuration& aConfig, Configuration& aValidatedConfig)
+{
+  nsAutoTArray<Size, 16> supportedSizes;
+  Get(CAMERA_PARAM_SUPPORTED_PICTURESIZES, supportedSizes);
+
+  nsresult rv = GetSupportedSize(aConfig.mPictureSize, supportedSizes,
+                                 aValidatedConfig.mPictureSize);
+  if (NS_FAILED(rv)) {
+    DOM_CAMERA_LOGW("Unable to find a picture size close to %ux%u\n",
+      aConfig.mPictureSize.width, aConfig.mPictureSize.height);
+    return NS_ERROR_INVALID_ARG;
+  }
+
+  rv = LoadRecorderProfiles();
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  nsString profileName = aConfig.mRecorderProfile;
+  if (profileName.IsEmpty()) {
+    profileName.AssignASCII("default");
+  }
+
+  RecorderProfile* profile;
+  if (!mRecorderProfiles.Get(profileName, &profile)) {
+    DOM_CAMERA_LOGE("Recorder profile '%s' is not supported\n",
+      NS_ConvertUTF16toUTF8(aConfig.mRecorderProfile).get());
+    return NS_ERROR_INVALID_ARG;
+  }
+
+  aValidatedConfig.mMode = aConfig.mMode;
+  aValidatedConfig.mPreviewSize = aConfig.mPreviewSize;
+  aValidatedConfig.mRecorderProfile = profile->GetName();
+  return NS_OK;
+}
+
+nsresult
 nsGonkCameraControl::SetConfigurationInternal(const Configuration& aConfig)
 {
   DOM_CAMERA_LOGT("%s:%d\n", __func__, __LINE__);
 
-  nsresult rv;
+  // Ensure sanity of all provided parameters and determine defaults if
+  // none are provided when given a new configuration
+  Configuration config;
+  nsresult rv = ValidateConfiguration(aConfig, config);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
 
   {
     ICameraControlParameterSetAutoEnter set(this);
 
-    switch (aConfig.mMode) {
+    switch (config.mMode) {
       case kPictureMode:
-        rv = SetPictureConfiguration(aConfig);
+        rv = SetPictureConfiguration(config);
         break;
 
       case kVideoMode:
-        rv = SetVideoConfiguration(aConfig);
+        rv = SetVideoConfiguration(config);
         break;
 
       default:
         MOZ_ASSERT_UNREACHABLE("Unanticipated camera mode in SetConfigurationInternal()");
         rv = NS_ERROR_FAILURE;
         break;
     }
 
     DOM_CAMERA_LOGT("%s:%d\n", __func__, __LINE__);
     if (NS_WARN_IF(NS_FAILED(rv))) {
       return rv;
     }
 
-    rv = Set(CAMERA_PARAM_RECORDINGHINT, aConfig.mMode == kVideoMode);
+    rv = Set(CAMERA_PARAM_RECORDINGHINT, config.mMode == kVideoMode);
     if (NS_FAILED(rv)) {
       DOM_CAMERA_LOGE("Failed to set recording hint (0x%x)\n", rv);
     }
   }
 
-  mCurrentConfiguration.mMode = aConfig.mMode;
-  mCurrentConfiguration.mRecorderProfile = aConfig.mRecorderProfile;
-
-  OnConfigurationChange();
+  mCurrentConfiguration.mMode = config.mMode;
+  mCurrentConfiguration.mRecorderProfile = config.mRecorderProfile;
+  mCurrentConfiguration.mPictureSize = config.mPictureSize;
   return NS_OK;
 }
 
 nsresult
 nsGonkCameraControl::SetConfigurationImpl(const Configuration& aConfig)
 {
   DOM_CAMERA_LOGT("%s:%d\n", __func__, __LINE__);
   MOZ_ASSERT(NS_GetCurrentThread() == mCameraThread);
@@ -330,17 +382,28 @@ nsGonkCameraControl::SetConfigurationImp
   rv = SetConfigurationInternal(aConfig);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     StopPreviewImpl();
     return rv;
   }
 
   // Restart the preview
   DOM_CAMERA_LOGT("%s:%d\n", __func__, __LINE__);
-  return StartPreviewImpl();
+  rv = StartPreviewInternal();
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    StopPreviewImpl();
+    return rv;
+  }
+
+  // OnConfigurationChange() indicates the success case of this operation.
+  // It must not be fired until all intermediate steps, including starting
+  // the preview, have completed successfully.
+  OnConfigurationChange();
+  OnPreviewStateChange(CameraControlListener::kPreviewStarted);
+  return NS_OK;
 }
 
 nsresult
 nsGonkCameraControl::MaybeAdjustVideoSize()
 {
   MOZ_ASSERT(NS_GetCurrentThread() == mCameraThread);
   MOZ_ASSERT(mSeparateVideoAndPreviewSizesSupported);
 
@@ -407,49 +470,39 @@ nsGonkCameraControl::MaybeAdjustVideoSiz
 }
 
 nsresult
 nsGonkCameraControl::SetPictureConfiguration(const Configuration& aConfig)
 {
   DOM_CAMERA_LOGT("%s:%d\n", __func__, __LINE__);
   MOZ_ASSERT(NS_GetCurrentThread() == mCameraThread);
 
-  nsTArray<Size> sizes;
-  nsresult rv = Get(CAMERA_PARAM_SUPPORTED_PREVIEWSIZES, sizes);
+  Size max({0, 0});
+  nsresult rv = SelectCaptureAndPreviewSize(aConfig.mPreviewSize,
+                                            aConfig.mPictureSize, max,
+                                            CAMERA_PARAM_PICTURE_SIZE);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
-  Size preview;
-  rv = GetSupportedSize(aConfig.mPreviewSize, sizes, preview);
-  if (NS_FAILED(rv)) {
-    DOM_CAMERA_LOGE(
-      "Failed to find a supported preview size, requested size %ux%u (0x%x)",
-      aConfig.mPreviewSize.width, aConfig.mPreviewSize.height, rv);
-    return rv;
+  if (mSeparateVideoAndPreviewSizesSupported) {
+    MaybeAdjustVideoSize();
   }
 
-  rv = Set(CAMERA_PARAM_PREVIEWSIZE, preview);
-  if (NS_FAILED(rv)) {
-    DOM_CAMERA_LOGE("Failed to set supported preview size %ux%u (0x%x)",
-                    preview.width, preview.height, rv);
+  rv = UpdateThumbnailSize();
+  if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
-  mCurrentConfiguration.mPreviewSize = preview;
-
-  if (mSeparateVideoAndPreviewSizesSupported) {
-    MaybeAdjustVideoSize();
-  }
-
   mParams.Get(CAMERA_PARAM_PREVIEWFRAMERATE, mPreviewFps);
 
   DOM_CAMERA_LOGI("picture mode preview: wanted %ux%u, got %ux%u (%u fps)\n",
                   aConfig.mPreviewSize.width, aConfig.mPreviewSize.height,
-                  preview.width, preview.height,
+                  mCurrentConfiguration.mPreviewSize.width,
+                  mCurrentConfiguration.mPreviewSize.height,
                   mPreviewFps);
 
   return NS_OK;
 }
 
 // Parameter management.
 nsresult
 nsGonkCameraControl::PushParameters()
@@ -678,17 +731,17 @@ nsGonkCameraControl::Get(uint32_t aKey, 
 // GPS location parameter accessors.
 nsresult
 nsGonkCameraControl::SetLocation(const Position& aLocation)
 {
   return SetAndPush(CAMERA_PARAM_PICTURE_LOCATION, aLocation);
 }
 
 nsresult
-nsGonkCameraControl::StartPreviewImpl()
+nsGonkCameraControl::StartPreviewInternal()
 {
   MOZ_ASSERT(NS_GetCurrentThread() == mCameraThread);
   RETURN_IF_NO_CAMERA_HW();
 
   ReentrantMonitorAutoEnter mon(mReentrantMonitor);
 
   if (mPreviewState == CameraControlListener::kPreviewStarted) {
     DOM_CAMERA_LOGW("Camera preview already started, nothing to do\n");
@@ -697,21 +750,30 @@ nsGonkCameraControl::StartPreviewImpl()
 
   DOM_CAMERA_LOGI("Starting preview (this=%p)\n", this);
 
   if (mCameraHw->StartPreview() != OK) {
     DOM_CAMERA_LOGE("Failed to start camera preview\n");
     return NS_ERROR_FAILURE;
   }
 
-  OnPreviewStateChange(CameraControlListener::kPreviewStarted);
   return NS_OK;
 }
 
 nsresult
+nsGonkCameraControl::StartPreviewImpl()
+{
+  nsresult rv = StartPreviewInternal();
+  if (NS_SUCCEEDED(rv)) {
+    OnPreviewStateChange(CameraControlListener::kPreviewStarted);
+  }
+  return rv;
+}
+
+nsresult
 nsGonkCameraControl::StopPreviewImpl()
 {
   RETURN_IF_NO_CAMERA_HW();
 
   DOM_CAMERA_LOGI("Stopping preview (this=%p)\n", this);
 
   mCameraHw->StopPreview();
   OnPreviewStateChange(CameraControlListener::kPreviewStopped);
@@ -809,18 +871,18 @@ nsGonkCameraControl::SetThumbnailSizeImp
   Get(CAMERA_PARAM_SUPPORTED_JPEG_THUMBNAIL_SIZES, supportedSizes);
 
   for (uint32_t i = 0; i < supportedSizes.Length(); ++i) {
     int area = supportedSizes[i].width * supportedSizes[i].height;
     int delta = abs(area - targetArea);
 
     if (area != 0 &&
         delta < smallestDelta &&
-        supportedSizes[i].width * mLastPictureSize.height ==
-          mLastPictureSize.width * supportedSizes[i].height) {
+        supportedSizes[i].width * mCurrentConfiguration.mPictureSize.height ==
+          mCurrentConfiguration.mPictureSize.width * supportedSizes[i].height) {
       smallestDelta = delta;
       smallestDeltaIndex = i;
     }
   }
 
   if (smallestDeltaIndex == UINT32_MAX) {
     DOM_CAMERA_LOGW("Unable to find a thumbnail size close to %ux%u, disabling thumbnail\n",
       aSize.width, aSize.height);
@@ -893,17 +955,18 @@ nsGonkCameraControl::SetPictureSizeImpl(
    * so if either is not specified, ignore both and go with current or
    * default settings.
    */
   if (!aSize.width || !aSize.height) {
     DOM_CAMERA_LOGW("Ignoring requested picture size of %ux%u\n", aSize.width, aSize.height);
     return NS_ERROR_INVALID_ARG;
   }
 
-  if (aSize.width == mLastPictureSize.width && aSize.height == mLastPictureSize.height) {
+  if (aSize.width == mCurrentConfiguration.mPictureSize.width &&
+      aSize.height == mCurrentConfiguration.mPictureSize.height) {
     DOM_CAMERA_LOGI("Requested picture size %ux%u unchanged\n", aSize.width, aSize.height);
     return NS_OK;
   }
 
   nsAutoTArray<Size, 8> supportedSizes;
   Get(CAMERA_PARAM_SUPPORTED_PICTURESIZES, supportedSizes);
 
   Size best;
@@ -921,17 +984,17 @@ nsGonkCameraControl::SetPictureSizeImpl(
     return NS_ERROR_FAILURE;
   }
 
   rv = mParams.Set(CAMERA_PARAM_PICTURE_SIZE, best);
   if (NS_FAILED(rv)) {
     return rv;
   }
 
-  mLastPictureSize = best;
+  mCurrentConfiguration.mPictureSize = best;
 
   // Finally, update the thumbnail size in case the picture aspect ratio changed.
   // Some drivers will fail to take a picture if the thumbnail size is not the
   // same aspect ratio as the picture size.
   return UpdateThumbnailSize();
 }
 
 int32_t
@@ -1372,16 +1435,21 @@ nsGonkCameraControl::GetSupportedSize(co
                                       const nsTArray<Size>& aSupportedSizes,
                                       Size& best)
 {
   nsresult rv = NS_ERROR_INVALID_ARG;
   best = aSize;
   uint32_t minSizeDelta = UINT32_MAX;
   uint32_t delta;
 
+  if (aSupportedSizes.IsEmpty()) {
+    // no valid sizes
+    return rv;
+  }
+
   if (!aSize.width && !aSize.height) {
     // no size specified, take the first supported size
     best = aSupportedSizes[0];
     return NS_OK;
   } else if (aSize.width && aSize.height) {
     // both height and width specified, find the supported size closest to
     // the requested size, looking for an exact match first
     for (SizeIndex i = 0; i < aSupportedSizes.Length(); ++i) {
@@ -1427,119 +1495,75 @@ nsGonkCameraControl::GetSupportedSize(co
       }
     }
   }
 
   return rv;
 }
 
 nsresult
-nsGonkCameraControl::SetVideoAndPreviewSize(const Size& aPreviewSize, const Size& aVideoSize)
+nsGonkCameraControl::SelectCaptureAndPreviewSize(const Size& aPreviewSize,
+                                                 const Size& aCaptureSize,
+                                                 const Size& aMaxSize,
+                                                 uint32_t aCaptureSizeKey)
 {
   MOZ_ASSERT(NS_GetCurrentThread() == mCameraThread);
-  MOZ_ASSERT(mSeparateVideoAndPreviewSizesSupported);
 
-  DOM_CAMERA_LOGI("Setting video size to %ux%u, preview size to %ux%u\n",
-                  aVideoSize.width, aVideoSize.height,
-                  aPreviewSize.width, aPreviewSize.height);
+  // At this point, we know the capture size has been validated and replaced
+  // if necessary with the best matching supported value.
+  DOM_CAMERA_LOGI("Select capture size %ux%u, preview size %ux%u, maximum size %ux%u\n",
+                  aCaptureSize.width, aCaptureSize.height,
+                  aPreviewSize.width, aPreviewSize.height,
+                  aMaxSize.width, aMaxSize.height);
 
-  Size oldSize;
-  nsresult rv = Get(CAMERA_PARAM_PREVIEWSIZE, oldSize);
+  nsAutoTArray<Size, 16> sizes;
+  nsresult rv = Get(CAMERA_PARAM_SUPPORTED_PREVIEWSIZES, sizes);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
-  rv = Set(CAMERA_PARAM_PREVIEWSIZE, aPreviewSize);
-  if (NS_WARN_IF(NS_FAILED(rv))) {
-    return rv;
-  }
-  rv = Set(CAMERA_PARAM_VIDEOSIZE, aVideoSize);
-  if (NS_WARN_IF(NS_FAILED(rv))) {
-    Set(CAMERA_PARAM_VIDEOSIZE, oldSize); // error, try to restore the original preview size
-    return rv;
-  }
-
-  mCurrentConfiguration.mPreviewSize = aPreviewSize;
-  mLastRecorderSize = aVideoSize;
-
-  return NS_OK;
-}
-
-nsresult
-nsGonkCameraControl::SelectVideoAndPreviewSize(const Configuration& aConfig, const Size& aVideoSize)
-{
-  MOZ_ASSERT(NS_GetCurrentThread() == mCameraThread);
-  MOZ_ASSERT(mSeparateVideoAndPreviewSizesSupported);
-
-  nsTArray<Size> sizes;
-
-  nsresult rv = Get(CAMERA_PARAM_SUPPORTED_VIDEOSIZES, sizes);
-  if (NS_WARN_IF(NS_FAILED(rv))) {
-    return rv;
+  // May optionally apply a ceiling to the preview size. Any supported preview
+  // size with an area larger than the maximum will be ignored regardless of
+  // aspect ratio or delta to requested preview size.
+  uint32_t maxArea = aMaxSize.width * aMaxSize.height;
+  if (maxArea == 0) {
+    maxArea = UINT32_MAX;
   }
 
-  Size video;
-  rv = GetSupportedSize(aVideoSize, sizes, video);
-  if (NS_FAILED(rv)) {
-    DOM_CAMERA_LOGE("Failed to find a supported video size, requested size %ux%u",
-                    aVideoSize.width, aVideoSize.height);
-    return rv;
-  }
-
-  rv = Get(CAMERA_PARAM_SUPPORTED_PREVIEWSIZES, sizes);
-  if (NS_WARN_IF(NS_FAILED(rv))) {
-    return rv;
-  }
-
-  Size preview;
-  rv = GetSupportedSize(aConfig.mPreviewSize, sizes, preview);
-  if (NS_FAILED(rv)) {
-    DOM_CAMERA_LOGE("Failed to find a supported preview size, requested size %ux%u",
-                    aConfig.mPreviewSize.width, aConfig.mPreviewSize.height);
-    return rv;
-  }
+  const uint32_t previewArea = aPreviewSize.width * aPreviewSize.height;
 
-  Size preferred;
-  rv = Get(CAMERA_PARAM_PREFERRED_PREVIEWSIZE_FOR_VIDEO, preferred);
-  if (NS_WARN_IF(NS_FAILED(rv))) {
-    return rv;
-  }
-
-  // If the requested preview size has the same aspect ratio as the
-  // requested video size, *and* is the same size or smaller than
-  // the preferred video size, then we're done.
-  const uint32_t preferredArea = preferred.width * preferred.height;
-  if (video.width * aConfig.mPreviewSize.height == aConfig.mPreviewSize.width * video.height &&
-      preview.width * preview.height <= preferredArea) {
-    // We're done: set the video and preview sizes and return...
-    return SetVideoAndPreviewSize(preview, video);
-  }
-
-  // Otherwise, if the requested preview size is larger than the preferred
-  // size, or there is an aspect ratio mismatch, then we need to set the
-  // preview size to the closest size smaller than the preferred size,
-  // preferably with the same aspect ratio as the requested video size.
+  // We should select a preview size with the same aspect ratio as the capture
+  // size and minimize the delta with the requested preview size. If we are
+  // unable to find any supported preview sizes which match the aspect ratio
+  // of the capture size, we fallback to only minimizing the delta with the
+  // requested preview size.
 
   SizeIndex bestSizeMatch = 0; // initializers to keep warnings away
   SizeIndex bestSizeMatchWithAspectRatio = 0;
   bool foundSizeMatch = false;
   bool foundSizeMatchWithAspectRatio = false;
 
   uint32_t bestAreaDelta = UINT32_MAX;
   uint32_t bestAreaDeltaWithAspect = UINT32_MAX;
 
   for (SizeIndex i = 0; i < sizes.Length(); ++i) {
     const Size& s = sizes[i];
-    const uint32_t area = s.width * s.height;
-    if (area > preferredArea) {
+
+    // preview size must be smaller or equal to the capture size
+    if (aCaptureSize.width < s.width || aCaptureSize.height < s.height) {
       continue;
     }
 
-    const uint32_t delta = preferredArea - area;
-    if (s.width * video.height == video.width * s.height) {
+    const uint32_t area = s.width * s.height;
+    if (area > maxArea) {
+      continue;
+    }
+
+    const uint32_t delta = abs(static_cast<long int>(previewArea - area));
+    if (s.width * aCaptureSize.height == aCaptureSize.width * s.height) {
       if (delta == 0) {
         // exact match, including aspect ratio--we can stop now
         bestSizeMatchWithAspectRatio = i;
         foundSizeMatchWithAspectRatio = true;
         break;
       } else if (delta < bestAreaDeltaWithAspect) {
         // aspect ratio match
         bestAreaDeltaWithAspect = delta;
@@ -1548,43 +1572,58 @@ nsGonkCameraControl::SelectVideoAndPrevi
       }
     } else if (delta < bestAreaDelta) {
       bestAreaDelta = delta;
       bestSizeMatch = i;
       foundSizeMatch = true;
     }
   }
 
+  Size previewSize;
   if (foundSizeMatchWithAspectRatio) {
-    preview = sizes[bestSizeMatchWithAspectRatio];
+    previewSize = sizes[bestSizeMatchWithAspectRatio];
   } else if (foundSizeMatch) {
-    DOM_CAMERA_LOGW("Unable to match a preview size with aspect ratio of video size %ux%u\n",
-      video.width, video.height);
-    preview = sizes[bestSizeMatch];
+    DOM_CAMERA_LOGW("Unable to match a preview size with aspect ratio of capture size %ux%u\n",
+      aCaptureSize.width, aCaptureSize.height);
+    previewSize = sizes[bestSizeMatch];
   } else {
-    DOM_CAMERA_LOGE("Unable to find a preview size for video size %ux%u\n",
-      video.width, video.height);
+    DOM_CAMERA_LOGE("Unable to find a preview size for capture size %ux%u\n",
+      aCaptureSize.width, aCaptureSize.height);
     return NS_ERROR_INVALID_ARG;
   }
 
-  return SetVideoAndPreviewSize(preview, video);
+  DOM_CAMERA_LOGI("Setting capture size to %ux%u, preview size to %ux%u\n",
+                  aCaptureSize.width, aCaptureSize.height,
+                  previewSize.width, previewSize.height);
+
+  Size oldSize;
+  rv = Get(CAMERA_PARAM_PREVIEWSIZE, oldSize);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  rv = Set(CAMERA_PARAM_PREVIEWSIZE, previewSize);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+  rv = Set(aCaptureSizeKey, aCaptureSize);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    Set(CAMERA_PARAM_PREVIEWSIZE, oldSize); // error, try to restore the original preview size
+    return rv;
+  }
+
+  mCurrentConfiguration.mPreviewSize = previewSize;
+  return NS_OK;
 }
 
 nsresult
 nsGonkCameraControl::SetVideoConfiguration(const Configuration& aConfig)
 {
   DOM_CAMERA_LOGT("%s:%d\n", __func__, __LINE__);
 
-  // The application may cache an old configuration and already have
-  // a desired recorder profile without checking the capabilities first
-  nsresult rv = LoadRecorderProfiles();
-  if (NS_WARN_IF(NS_FAILED(rv))) {
-    return rv;
-  }
-
   RecorderProfile* profile;
   if (!mRecorderProfiles.Get(aConfig.mRecorderProfile, &profile)) {
     DOM_CAMERA_LOGE("Recorder profile '%s' is not supported\n",
       NS_ConvertUTF16toUTF8(aConfig.mRecorderProfile).get());
     return NS_ERROR_INVALID_ARG;
   }
 
   const RecorderProfile::Video& video(profile->GetVideo());
@@ -1600,17 +1639,24 @@ nsGonkCameraControl::SetVideoConfigurati
 
   {
     ICameraControlParameterSetAutoEnter set(this);
     nsresult rv;
 
     if (mSeparateVideoAndPreviewSizesSupported) {
       // The camera supports two video streams: a low(er) resolution preview
       // stream and and a potentially high(er) resolution stream for encoding.
-      rv = SelectVideoAndPreviewSize(aConfig, size);
+      Size preferred;
+      rv = Get(CAMERA_PARAM_PREFERRED_PREVIEWSIZE_FOR_VIDEO, preferred);
+      if (NS_WARN_IF(NS_FAILED(rv))) {
+        return rv;
+      }
+
+      rv = SelectCaptureAndPreviewSize(aConfig.mPreviewSize, size, preferred,
+                                       CAMERA_PARAM_VIDEOSIZE);
       if (NS_FAILED(rv)) {
         DOM_CAMERA_LOGE("Failed to set video and preview sizes (0x%x)\n", rv);
         return rv;
       }
     } else {
       // The camera only supports a single video stream: in this case, we set
       // the preview size to be the desired video recording size, and ignore
       // the specified preview size.
@@ -1618,16 +1664,18 @@ nsGonkCameraControl::SetVideoConfigurati
       if (NS_FAILED(rv)) {
         DOM_CAMERA_LOGE("Failed to set video mode preview size (0x%x)\n", rv);
         return rv;
       }
 
       mCurrentConfiguration.mPreviewSize = size;
     }
 
+    mLastRecorderSize = size;
+
     rv = Set(CAMERA_PARAM_PREVIEWFRAMERATE, static_cast<int>(fps));
     if (NS_FAILED(rv)) {
       DOM_CAMERA_LOGE("Failed to set video mode frame rate (0x%x)\n", rv);
       return rv;
     }
   }
 
   mPreviewFps = fps;
@@ -1898,33 +1946,48 @@ nsGonkCameraControl::LoadRecorderProfile
     }
 
     nsTArray<Size> sizes;
     rv = Get(CAMERA_PARAM_SUPPORTED_VIDEOSIZES, sizes);
     if (NS_WARN_IF(NS_FAILED(rv))) {
       return NS_ERROR_NOT_AVAILABLE;
     }
 
+    nsTArray<RecorderProfile>::size_type bestIndexMatch = 0;
+    int bestAreaMatch = 0;
+
     // Limit profiles to those video sizes supported by the camera hardware...
     for (nsTArray<RecorderProfile>::size_type i = 0; i < profiles.Length(); ++i) {
-      int width = profiles[i]->GetVideo().GetSize().width; 
+      int width = profiles[i]->GetVideo().GetSize().width;
       int height = profiles[i]->GetVideo().GetSize().height;
       if (width < 0 || height < 0) {
         DOM_CAMERA_LOGW("Ignoring weird profile '%s' with width and/or height < 0\n",
           NS_ConvertUTF16toUTF8(profiles[i]->GetName()).get());
         continue;
       }
       for (nsTArray<Size>::size_type n = 0; n < sizes.Length(); ++n) {
         if (static_cast<uint32_t>(width) == sizes[n].width &&
             static_cast<uint32_t>(height) == sizes[n].height) {
           mRecorderProfiles.Put(profiles[i]->GetName(), profiles[i]);
+          int area = width * height;
+          if (area > bestAreaMatch) {
+            bestIndexMatch = i;
+            bestAreaMatch = area;
+          }
           break;
         }
       }
     }
+
+    // Default profile is the one with the largest area.
+    if (bestAreaMatch > 0) {
+      nsAutoString name;
+      name.AssignASCII("default");
+      mRecorderProfiles.Put(name, profiles[bestIndexMatch]);
+    }
   }
 
   return NS_OK;
 }
 
 /* static */ PLDHashOperator
 nsGonkCameraControl::Enumerate(const nsAString& aProfileName,
                                RecorderProfile* aProfile,
--- a/dom/camera/GonkCameraControl.h
+++ b/dom/camera/GonkCameraControl.h
@@ -100,20 +100,22 @@ protected:
 
   typedef nsTArray<Size>::index_type SizeIndex;
 
   virtual void BeginBatchParameterSet() MOZ_OVERRIDE;
   virtual void EndBatchParameterSet() MOZ_OVERRIDE;
 
   nsresult Initialize();
 
+  nsresult ValidateConfiguration(const Configuration& aConfig, Configuration& aValidatedConfig);
   nsresult SetConfigurationInternal(const Configuration& aConfig);
   nsresult SetPictureConfiguration(const Configuration& aConfig);
   nsresult SetVideoConfiguration(const Configuration& aConfig);
   nsresult StartInternal(const Configuration* aInitialConfig);
+  nsresult StartPreviewInternal();
   nsresult StopInternal();
 
   template<class T> nsresult SetAndPush(uint32_t aKey, const T& aValue);
 
   // See CameraControlImpl.h for these methods' return values.
   virtual nsresult StartImpl(const Configuration* aInitialConfig = nullptr) MOZ_OVERRIDE;
   virtual nsresult SetConfigurationImpl(const Configuration& aConfig) MOZ_OVERRIDE;
   virtual nsresult StopImpl() MOZ_OVERRIDE;
@@ -128,18 +130,18 @@ protected:
   virtual nsresult StopRecordingImpl() MOZ_OVERRIDE;
   virtual nsresult ResumeContinuousFocusImpl() MOZ_OVERRIDE;
   virtual nsresult PushParametersImpl() MOZ_OVERRIDE;
   virtual nsresult PullParametersImpl() MOZ_OVERRIDE;
 
   nsresult SetupRecording(int aFd, int aRotation, uint64_t aMaxFileSizeBytes,
                           uint64_t aMaxVideoLengthMs);
   nsresult SetupRecordingFlash(bool aAutoEnableLowLightTorch);
-  nsresult SelectVideoAndPreviewSize(const Configuration& aConfig, const Size& aVideoSize);
-  nsresult SetVideoAndPreviewSize(const Size& aPreviewSize, const Size& aVideoSize);
+  nsresult SelectCaptureAndPreviewSize(const Size& aPreviewSize, const Size& aCaptureSize,
+                                       const Size& aMaxSize, uint32_t aCaptureSizeKey);
   nsresult MaybeAdjustVideoSize();
   nsresult PausePreview();
   nsresult GetSupportedSize(const Size& aSize, const nsTArray<Size>& supportedSizes, Size& best);
 
   nsresult LoadRecorderProfiles();
   static PLDHashOperator Enumerate(const nsAString& aProfileName,
                                    RecorderProfile* aProfile,
                                    void* aUserArg);
@@ -153,17 +155,16 @@ protected:
   nsresult SetThumbnailSizeImpl(const Size& aSize);
 
   int32_t RationalizeRotation(int32_t aRotation);
 
   uint32_t                  mCameraId;
 
   android::sp<android::GonkCameraHardware> mCameraHw;
 
-  Size                      mLastPictureSize;
   Size                      mLastThumbnailSize;
   Size                      mLastRecorderSize;
   uint32_t                  mPreviewFps;
   bool                      mResumePreviewAfterTakingPicture;
   bool                      mFlashSupported;
   bool                      mLuminanceSupported;
   bool                      mAutoFlashModeOverridden;
   bool                      mSeparateVideoAndPreviewSizesSupported;
--- a/dom/camera/ICameraControl.h
+++ b/dom/camera/ICameraControl.h
@@ -140,16 +140,17 @@ public:
     uint64_t  maxFileSizeBytes;
     uint64_t  maxVideoLengthMs;
     bool      autoEnableLowLightTorch;
   };
 
   struct Configuration {
     Mode      mMode;
     Size      mPreviewSize;
+    Size      mPictureSize;
     nsString  mRecorderProfile;
   };
 
   struct Point {
     int32_t   x;
     int32_t   y;
   };
 
--- a/dom/camera/test/mochitest.ini
+++ b/dom/camera/test/mochitest.ini
@@ -8,8 +8,10 @@ support-files = camera_common.js
 [test_camera_hardware_failures.html]
 [test_bug975472.html]
 [test_camera_fake_parameters.html]
 [test_camera_hardware_face_detection.html]
 [test_camera_hardware_auto_focus_moving_cb.html]
 [test_bug1022766.html]
 [test_bug1037322.html]
 [test_bug1099390.html]
+[test_bug1104913.html]
+[test_camera_bad_initial_config.html]
--- a/dom/camera/test/test_bug1022766.html
+++ b/dom/camera/test/test_bug1022766.html
@@ -10,17 +10,17 @@
 <body>
 <video id="viewfinder" width="200" height="200" autoplay></video>
 <img src="#" alt="This image is going to load" id="testimage"/>
 <script class="testbody" type="text/javascript;version=1.7">
 
 var whichCamera = navigator.mozCameras.getListOfCameras()[0];
 var config = {
   mode: 'picture',
-  recorderProfile: 'cif',
+  recorderProfile: 'high',
   previewSize: {
     width: 352,
     height: 288
   }
 };
 
 function onError(e) {
   ok(false, "Error " + e);
--- a/dom/camera/test/test_bug1037322.html
+++ b/dom/camera/test/test_bug1037322.html
@@ -10,20 +10,20 @@
 <body>
 <video id="viewfinder" width="200" height="200" autoplay></video>
 <img src="#" alt="This image is going to load" id="testimage"/>
 <script class="testbody" type="text/javascript;version=1.7">
 
 var whichCamera = navigator.mozCameras.getListOfCameras()[0];
 var config = {
   mode: 'picture',
-  recorderProfile: 'cif',
+  recorderProfile: 'high',
   previewSize: {
-    width: 352,
-    height: 288
+    width: 320,
+    height: 240
   }
 };
 
 function onError(e) {
   ok(false, "Error: " + JSON.stringify(e));
 }
 
 var Camera = {
@@ -52,24 +52,27 @@ var Camera = {
       Camera.cameraObj = camera;
       Camera.viewfinder.mozSrcObject = camera;
       Camera.viewfinder.play();
 
       // Check the default configuration
       ok(cfg.mode === "unspecified", "Initial mode = " + cfg.mode);
       ok(cfg.previewSize.width === 0 && cfg.previewSize.height === 0,
          "Initial preview size = " + cfg.previewSize.width + "x" + cfg.previewSize.height);
-      ok(cfg.recorderProfile === "",
+      ok(cfg.recorderProfile === "default",
          "Initial recorder profile = '" + cfg.recorderProfile + "'");
 
       // Apply our specific configuration
       camera.setConfiguration(config).then(setConfig_onSuccess, onError);
     }
 
-    navigator.mozCameras.getCamera(whichCamera, {}).then(getCamera_onSuccess, onError);
+    var cfg = {
+      mode: 'unspecified',
+    };
+    navigator.mozCameras.getCamera(whichCamera, cfg).then(getCamera_onSuccess, onError);
   }
 }
 
 SimpleTest.waitForExplicitFinish();
 
 window.addEventListener('beforeunload', function() {
   Camera.viewfinder.mozSrcObject = null;
   if (Camera.cameraObj) {
--- a/dom/camera/test/test_bug1099390.html
+++ b/dom/camera/test/test_bug1099390.html
@@ -10,17 +10,17 @@
 <body>
 <video id="viewfinder" width="200" height="200" autoplay></video>
 <img src="#" alt="This image is going to load" id="testimage"/>
 <script class="testbody" type="text/javascript;version=1.7">
 
 var whichCamera = navigator.mozCameras.getListOfCameras()[0];
 var config = {
   mode: 'picture',
-  recorderProfile: 'cif',
+  recorderProfile: 'high',
   previewSize: {
     width: 352,
     height: 288
   }
 };
 
 function onError(e) {
   ok(false, "Error " + e);
new file mode 100644
--- /dev/null
+++ b/dom/camera/test/test_bug1104913.html
@@ -0,0 +1,81 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <title>Test for bug 1104913</title>
+  <script type="text/javascript" src="/MochiKit/MochiKit.js"></script>
+  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <script type="text/javascript" src="camera_common.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<video id="viewfinder" width="200" height="200" autoplay></video>
+<img src="#" alt="This image is going to load" id="testimage"/>
+<script class="testbody" type="text/javascript;version=1.7">
+
+var whichCamera = navigator.mozCameras.getListOfCameras()[0];
+var config = {
+  mode: 'picture',
+  recorderProfile: 'qvga',
+  pictureSize: {
+    width: 640,
+    height: 480
+  },
+  previewSize: {
+    width: 320,
+    height: 240
+  }
+};
+
+function onError(e) {
+  ok(false, "Error: " + JSON.stringify(e));
+}
+
+var Camera = {
+  cameraObj: null,
+
+  get viewfinder() {
+    return document.getElementById('viewfinder');
+  },
+
+  start: function test_start() {
+    function getCamera_onSuccess(d) {
+      var camera = d.camera;
+      var cfg = d.configuration;
+      Camera.cameraObj = camera;
+      Camera.viewfinder.mozSrcObject = camera;
+      Camera.viewfinder.play();
+
+      // Check the default configuration
+      ok(cfg.mode === config.mode, "Initial mode = " + cfg.mode);
+      ok(cfg.previewSize.width === config.previewSize.width &&
+         cfg.previewSize.height === config.previewSize.height,
+         "Initial preview size = " + cfg.previewSize.width + "x" + cfg.previewSize.height);
+      ok(cfg.pictureSize.width === config.pictureSize.width &&
+         cfg.pictureSize.height === config.pictureSize.height,
+         "Initial picture size = " + cfg.pictureSize.width + "x" + cfg.pictureSize.height);
+      ok(cfg.recorderProfile === config.recorderProfile,
+         "Initial recorder profile = '" + cfg.recorderProfile + "'");
+
+      SimpleTest.finish();
+    }
+
+    navigator.mozCameras.getCamera(whichCamera, {}).then(getCamera_onSuccess, onError);
+  }
+}
+
+SimpleTest.waitForExplicitFinish();
+
+window.addEventListener('beforeunload', function() {
+  Camera.viewfinder.mozSrcObject = null;
+  if (Camera.cameraObj) {
+    Camera.cameraObj.release();
+    Camera.cameraObj = null;
+  }
+});
+
+Camera.start();
+
+</script>
+</body>
+
+</html>
--- a/dom/camera/test/test_bug975472.html
+++ b/dom/camera/test/test_bug975472.html
@@ -12,20 +12,20 @@
 <img src="#" alt="This image is going to load" id="testimage"/>
 <script class="testbody" type="text/javascript;version=1.7">
 
 const Cr = Components.results;
 
 var whichCamera = navigator.mozCameras.getListOfCameras()[0];
 var config = {
   mode: 'picture',
-  recorderProfile: 'cif',
+  recorderProfile: 'high',
   previewSize: {
-    width: 352,
-    height: 288
+    width: 320,
+    height: 240
   }
 };
 var options = {
   rotation: 0,
   position: {
     latitude: 43.645687,
     longitude: -79.393661
   },
@@ -132,17 +132,17 @@ var tests = [
         ok(false, "startRecording() process succeeded incorrectly");
       }
       function onError(error) {
         ok(error.name === "NS_ERROR_NOT_AVAILABLE",
           "startRecording() failed with: " + error.name);
         next();
       }
       var recordingOptions = {
-        profile: 'cif',
+        profile: 'high',
         rotation: 0
       };
       camera.startRecording(recordingOptions,
                             navigator.getDeviceStorage('videos'),
                             'bug975472.mp4').then(onSuccess, onError);
     }
   },
   {
--- a/dom/camera/test/test_camera.html
+++ b/dom/camera/test/test_camera.html
@@ -9,20 +9,20 @@
 <body>
 <video id="viewfinder" width="200" height="200" autoplay></video>
 <img src="#" alt="This image is going to load" id="testimage"/>
 <script class="testbody" type="text/javascript;version=1.7">
 
 var whichCamera = navigator.mozCameras.getListOfCameras()[0];
 var options = {
   mode: 'picture',
-  recorderProfile: 'cif',
+  recorderProfile: 'high',
   previewSize: {
-    width: 352,
-    height: 288
+    width: 320,
+    height: 240
   }
 };
 
 var config = {
   dateTime: Date.now() / 1000,
   pictureSize: null,
   fileFormat: 'jpeg',
   rotation: 90
--- a/dom/camera/test/test_camera_2.html
+++ b/dom/camera/test/test_camera_2.html
@@ -9,20 +9,20 @@
 <body>
 <video id="viewfinder" width="200" height="200" autoplay></video>
 <img src="#" alt="This image is going to load" id="testimage"/>
 <script class="testbody" type="text/javascript;version=1.7">
 
 var whichCamera = navigator.mozCameras.getListOfCameras()[0];
 var options = {
   mode: 'picture',
-  recorderProfile: 'cif',
+  recorderProfile: 'high',
   previewSize: {
-    width: 352,
-    height: 288
+    width: 320,
+    height: 240
   }
 };
 
 var config = {
   dateTime: Date.now() / 1000,
   pictureSize: null,
   fileFormat: 'jpeg',
   rotation: 90
--- a/dom/camera/test/test_camera_3.html
+++ b/dom/camera/test/test_camera_3.html
@@ -9,17 +9,17 @@
 <body>
 <video id="viewfinder" width="200" height="200" autoplay></video>
 <img src="#" alt="This image is going to load" id="testimage"/>
 <script class="testbody" type="text/javascript;version=1.7">
 
 var whichCamera = navigator.mozCameras.getListOfCameras()[0];
 var options = {
   mode: 'picture',
-  recorderProfile: 'cif',
+  recorderProfile: 'high',
   previewSize: {
     width: 352,
     height: 288
   }
 };
 
 function onError(e) {
   ok(false, "Error " + e);
new file mode 100644
--- /dev/null
+++ b/dom/camera/test/test_camera_bad_initial_config.html
@@ -0,0 +1,58 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <title>Test for bad initial configuration</title>
+  <script type="text/javascript" src="/MochiKit/MochiKit.js"></script>
+  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <script type="text/javascript" src="camera_common.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<video id="viewfinder" width="200" height="200" autoplay></video>
+<img src="#" alt="This image is going to load" id="testimage"/>
+<script class="testbody" type="text/javascript;version=1.7">
+
+var whichCamera = navigator.mozCameras.getListOfCameras()[0];
+var config = {
+  mode: 'picture',
+  recorderProfile: 'foobar',
+};
+
+var Camera = {
+  cameraObj: null,
+
+  get viewfinder() {
+    return document.getElementById('viewfinder');
+  },
+
+  start: function test_start() {
+    function getCamera_onSuccess(d) {
+      ok(false, "Get camera should have failed");
+      SimpleTest.finish();
+    }
+
+    function getCamera_onError(e) {
+      ok(true, "Get camera failed as expected: " + JSON.stringify(e));
+      SimpleTest.finish();
+    }
+
+    navigator.mozCameras.getCamera(whichCamera, config).then(getCamera_onSuccess, getCamera_onError);
+  }
+}
+
+SimpleTest.waitForExplicitFinish();
+
+window.addEventListener('beforeunload', function() {
+  Camera.viewfinder.mozSrcObject = null;
+  if (Camera.cameraObj) {
+    Camera.cameraObj.release();
+    Camera.cameraObj = null;
+  }
+});
+
+Camera.start();
+
+</script>
+</body>
+
+</html>
--- a/dom/camera/test/test_camera_fake_parameters.html
+++ b/dom/camera/test/test_camera_fake_parameters.html
@@ -12,17 +12,17 @@
   <video id="viewfinder" width="200" height="200" autoplay></video>
   <img src="#" alt="This image is going to load" id="testimage"/>
 
 <script class="testbody" type="text/javascript;version=1.7">
 
 var whichCamera = navigator.mozCameras.getListOfCameras()[0];
 var initialConfig = {
   mode: 'picture',
-  recorderProfile: 'cif',
+  recorderProfile: 'high',
   previewSize: {
     width: 352,
     height: 288
   }
 };
 
 var cameraObj = null;
 
--- a/dom/camera/test/test_camera_hardware_auto_focus_moving_cb.html
+++ b/dom/camera/test/test_camera_hardware_auto_focus_moving_cb.html
@@ -15,17 +15,17 @@ https://bugzilla.mozilla.org/show_bug.cg
   <video id="viewfinder" width = "200" height = "200" autoplay></video>
   <img src="#" alt="This image is going to load" id="testimage"/>
 
 <script class="testbody" type="text/javascript;version=1.7">
 
 var whichCamera = navigator.mozCameras.getListOfCameras()[0];
 var initialConfig = {
   mode: 'picture',
-  recorderProfile: 'cif',
+  recorderProfile: 'high',
   previewSize: {
     width: 352,
     height: 288
   }
 };
 
 const PREF_AUTOFOCUSCALLBACK_ENABLED = "camera.control.autofocus_moving_callback.enabled";
 
--- a/dom/camera/test/test_camera_hardware_face_detection.html
+++ b/dom/camera/test/test_camera_hardware_face_detection.html
@@ -15,17 +15,17 @@ https://bugzilla.mozilla.org/show_bug.cg
   <video id="viewfinder" width = "200" height = "200" autoplay></video>
   <img src="#" alt="This image is going to load" id="testimage"/>
 
 <script class="testbody" type="text/javascript;version=1.7">
 
 var whichCamera = navigator.mozCameras.getListOfCameras()[0];
 var initialConfig = {
   mode: 'picture',
-  recorderProfile: 'cif',
+  recorderProfile: 'high',
   previewSize: {
     width: 352,
     height: 288
   }
 };
 
 const PREF_FACEDETECTION_ENABLED = "camera.control.face_detection.enabled";
 
--- a/dom/camera/test/test_camera_hardware_failures.html
+++ b/dom/camera/test/test_camera_hardware_failures.html
@@ -15,17 +15,17 @@ https://bugzilla.mozilla.org/show_bug.cg
   <video id="viewfinder" width = "200" height = "200" autoplay></video>
   <img src="#" alt="This image is going to load" id="testimage"/>
 
 <script class="testbody" type="text/javascript;version=1.7">
 
 var whichCamera = navigator.mozCameras.getListOfCameras()[0];
 var initialConfig = {
   mode: 'picture',
-  recorderProfile: 'cif',
+  recorderProfile: 'high',
   previewSize: {
     width: 352,
     height: 288
   }
 };
 
 var cameraObj;
 
--- a/dom/camera/test/test_camera_hardware_init_failure.html
+++ b/dom/camera/test/test_camera_hardware_init_failure.html
@@ -17,17 +17,17 @@ https://bugzilla.mozilla.org/show_bug.cg
 
 <script class="testbody" type="text/javascript;version=1.7">
 
 SimpleTest.waitForExplicitFinish();
 
 var whichCamera = navigator.mozCameras.getListOfCameras()[0];
 var initialConfig = {
   mode: 'picture',
-  recorderProfile: 'cif',
+  recorderProfile: 'high',
   previewSize: {
     width: 352,
     height: 288
   }
 };
 
 var tests = [
   {
--- a/dom/webidl/CameraConfigurationEvent.webidl
+++ b/dom/webidl/CameraConfigurationEvent.webidl
@@ -5,16 +5,18 @@
 
 [Func="Navigator::HasCameraSupport",
  Constructor(DOMString type, optional CameraConfigurationEventInit eventInitDict)]
 interface CameraConfigurationEvent : Event
 {
   readonly attribute CameraMode mode;
   readonly attribute DOMString recorderProfile;
   readonly attribute DOMRectReadOnly? previewSize;
+  readonly attribute DOMRectReadOnly? pictureSize;
 };
 
 dictionary CameraConfigurationEventInit : EventInit
 {
   CameraMode mode = "picture";
   DOMString recorderProfile = "cif";
   DOMRectReadOnly? previewSize = null;
+  DOMRectReadOnly? pictureSize = null;
 };
--- a/dom/webidl/CameraControl.webidl
+++ b/dom/webidl/CameraControl.webidl
@@ -250,17 +250,20 @@ interface CameraControl : MediaStream
      useful for synchronizing other UI elements.
 
      event type is CameraStateChangeEvent where:
          'newState' is the new preview state */
   attribute EventHandler    onpreviewstatechange;
 
   /* the size of the picture to be returned by a call to takePicture();
      an object with 'height' and 'width' properties that corresponds to
-     one of the options returned by capabilities.pictureSizes. */
+     one of the options returned by capabilities.pictureSizes.
+
+     note that unlike when one uses setConfiguration instead to update the
+     picture size, this will not recalculate the ideal preview size. */
   [Throws]
   CameraSize getPictureSize();
   [Throws]
   void setPictureSize(optional CameraSize size);
 
   /* if the image blob to be returned by takePicture() supports lossy
      compression, this setting controls the quality-size trade-off;
      valid values range from 0.0 for smallest size/worst quality to 1.0
@@ -282,16 +285,17 @@ interface CameraControl : MediaStream
   CameraSize getThumbnailSize();
   [Throws]
   void setThumbnailSize(optional CameraSize size);
 
   /* the angle, in degrees, that the image sensor is mounted relative
      to the display; e.g. if 'sensorAngle' is 270 degrees (or -90 degrees),
      then the preview stream needs to be rotated +90 degrees to have the
      same orientation as the real world. */
+  [Constant, Cached]
   readonly attribute long   sensorAngle;
 
   /* the mode the camera will use to determine the correct exposure of
      the scene; supported modes are exposed by capabilities.meteringModes. */
   [Throws]
   attribute DOMString       meteringMode;
 
   /* tell the camera to attempt to focus the image */
@@ -355,21 +359,18 @@ interface CameraControl : MediaStream
   Promise<void> release();
 
   /* changes the camera configuration on the fly. */
   [Throws]
   Promise<CameraConfiguration> setConfiguration(optional CameraConfiguration configuration);
 
   /* the event dispatched when the camera is successfully configured.
 
-     event type is CameraConfigurationEvent where:
-         'mode' is the selected camera mode
-         'recorderProfile' is the selected profile
-         'width' contains the preview width
-         'height' contains the preview height */
+     event type is CameraConfigurationEvent which has the same members as
+     CameraConfiguration. */
   attribute EventHandler onconfigurationchange;
 
   /* if focusMode is set to either 'continuous-picture' or 'continuous-video',
      then calling autoFocus() will trigger its onSuccess callback immediately
      if the camera was either successfully focused, or if no focus could be
      acquired; if the focus acquisition is still in progress, the onSuccess
      callback will be invoked later, its argument indicating success or
      failure.
--- a/dom/webidl/CameraManager.webidl
+++ b/dom/webidl/CameraManager.webidl
@@ -10,23 +10,44 @@ enum CameraMode { "unspecified", "pictur
 /* Used for the dimensions of a captured picture,
    a preview stream, a video capture stream, etc. */
 dictionary CameraSize
 {
   unsigned long width = 0;
   unsigned long height = 0;
 };
 
-/* Pre-emptive camera configuration options. */
+/* Pre-emptive camera configuration options. If 'mode' is set to "unspecified",
+   the camera will not be configured immediately. If the 'mode' is set to
+   "video" or "picture", then the camera automatically configures itself and
+   will be ready for use upon return.
+
+   The remaining parameters are optional and are considered hints by the
+   camera. The application should use the values returned in the
+   GetCameraCallback configuration because while the camera makes a best effort
+   to adhere to the requested values, it may need to change them to ensure
+   optimal behavior.
+
+   If not specified, 'pictureSize' and 'recorderProfile' default to the best or
+   highest resolutions supported by the camera hardware.
+
+   To determine 'previewSize', one should generally provide the size of the
+   element which will contain the preview rather than guess which supported
+   preview size is the best. If not specified, 'previewSize' defaults to the
+   inner window size. */
 dictionary CameraConfiguration
 {
-  CameraMode mode = "unspecified";
+  CameraMode mode = "picture";
   CameraSize previewSize = null;
-  DOMString recorderProfile = ""; // one of the profiles reported by
-                                  // CameraControl.capabilities.recorderProfiles
+  CameraSize pictureSize = null;
+
+  /* one of the profiles reported by
+     CameraControl.capabilities.recorderProfiles
+  */
+  DOMString recorderProfile = "default";
 };
 
 [Func="nsDOMCameraManager::HasSupport"]
 interface CameraManager
 {
   /* get a camera instance; 'camera' is one of the camera
      identifiers returned by getListOfCameras() below.
   */