/* 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 "DOMCameraControl.h" #include "base/basictypes.h" #include "nsCOMPtr.h" #include "nsDOMClassInfo.h" #include "nsHashPropertyBag.h" #include "nsThread.h" #include "DeviceStorage.h" #include "DeviceStorageFileDescriptor.h" #include "mozilla/dom/File.h" #include "mozilla/dom/TabChild.h" #include "mozilla/ipc/FileDescriptorUtils.h" #include "mozilla/MediaManager.h" #include "mozilla/Services.h" #include "mozilla/unused.h" #include "nsIAppsService.h" #include "nsIObserverService.h" #include "nsIDOMEventListener.h" #include "nsIScriptSecurityManager.h" #include "Navigator.h" #include "nsXULAppAPI.h" #include "DOMCameraManager.h" #include "DOMCameraCapabilities.h" #include "CameraCommon.h" #include "nsGlobalWindow.h" #include "CameraPreviewMediaStream.h" #include "mozilla/dom/CameraUtilBinding.h" #include "mozilla/dom/CameraControlBinding.h" #include "mozilla/dom/CameraManagerBinding.h" #include "mozilla/dom/CameraCapabilitiesBinding.h" #include "mozilla/dom/CameraConfigurationEvent.h" #include "mozilla/dom/CameraConfigurationEventBinding.h" #include "mozilla/dom/CameraFacesDetectedEvent.h" #include "mozilla/dom/CameraFacesDetectedEventBinding.h" #include "mozilla/dom/CameraStateChangeEvent.h" #include "mozilla/dom/CameraClosedEvent.h" #include "mozilla/dom/BlobEvent.h" #include "DOMCameraDetectedFace.h" #include "mozilla/dom/BindingUtils.h" #include "nsPrintfCString.h" using namespace mozilla; using namespace mozilla::dom; using namespace mozilla::ipc; #ifdef MOZ_WIDGET_GONK StaticRefPtr nsDOMCameraControl::sCachedCameraControl; /* static */ nsresult nsDOMCameraControl::sCachedCameraControlStartResult = NS_OK; /* static */ nsCOMPtr nsDOMCameraControl::sDiscardCachedCameraControlTimer; #endif NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(nsDOMCameraControl) NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference) // nsISupports is an ambiguous base of nsDOMCameraControl // so we have to work around that. if ( aIID.Equals(NS_GET_IID(nsDOMCameraControl)) ) foundInterface = static_cast(static_cast(this)); else NS_INTERFACE_MAP_END_INHERITING(DOMMediaStream) NS_IMPL_ADDREF_INHERITED(nsDOMCameraControl, DOMMediaStream) NS_IMPL_RELEASE_INHERITED(nsDOMCameraControl, DOMMediaStream) NS_IMPL_CYCLE_COLLECTION_INHERITED(nsDOMCameraControl, DOMMediaStream, mAudioChannelAgent, mCapabilities, mWindow, mGetCameraPromise, mAutoFocusPromise, mTakePicturePromise, mStartRecordingPromise, mReleasePromise, mSetConfigurationPromise) /* static */ bool nsDOMCameraControl::HasSupport(JSContext* aCx, JSObject* aGlobal) { return Navigator::HasCameraSupport(aCx, aGlobal); } static nsresult RegisterStorageRequestEvents(DOMRequest* aRequest, nsIDOMEventListener* aListener) { EventListenerManager* elm = aRequest->GetOrCreateListenerManager(); if (NS_WARN_IF(!elm)) { return NS_ERROR_UNEXPECTED; } elm->AddEventListener(NS_LITERAL_STRING("success"), aListener, false, false); elm->AddEventListener(NS_LITERAL_STRING("error"), aListener, false, false); return NS_OK; } class mozilla::StartRecordingHelper : public nsIDOMEventListener { public: NS_DECL_ISUPPORTS NS_DECL_NSIDOMEVENTLISTENER explicit StartRecordingHelper(nsDOMCameraControl* aDOMCameraControl) : mDOMCameraControl(aDOMCameraControl) , mState(false) { MOZ_COUNT_CTOR(StartRecordingHelper); } protected: virtual ~StartRecordingHelper() { MOZ_COUNT_DTOR(StartRecordingHelper); mDOMCameraControl->OnCreatedFileDescriptor(mState); } protected: nsRefPtr mDOMCameraControl; bool mState; }; NS_IMETHODIMP StartRecordingHelper::HandleEvent(nsIDOMEvent* aEvent) { nsString eventType; aEvent->GetType(eventType); mState = eventType.EqualsLiteral("success"); return NS_OK; } NS_IMPL_ISUPPORTS(mozilla::StartRecordingHelper, nsIDOMEventListener) class mozilla::RecorderPosterHelper : public nsIDOMEventListener { public: NS_DECL_ISUPPORTS NS_DECL_NSIDOMEVENTLISTENER explicit RecorderPosterHelper(nsDOMCameraControl* aDOMCameraControl) : mDOMCameraControl(aDOMCameraControl) , mState(CameraControlListener::kPosterFailed) { MOZ_COUNT_CTOR(RecorderPosterHelper); } protected: virtual ~RecorderPosterHelper() { MOZ_COUNT_DTOR(RecorderPosterHelper); mDOMCameraControl->OnRecorderStateChange(mState, 0, 0); } protected: nsRefPtr mDOMCameraControl; CameraControlListener::RecorderState mState; }; NS_IMETHODIMP RecorderPosterHelper::HandleEvent(nsIDOMEvent* aEvent) { nsString eventType; aEvent->GetType(eventType); if (eventType.EqualsLiteral("success")) { mState = CameraControlListener::kPosterCreated; } return NS_OK; } NS_IMPL_ISUPPORTS(mozilla::RecorderPosterHelper, nsIDOMEventListener) nsDOMCameraControl::DOMCameraConfiguration::DOMCameraConfiguration() : CameraConfiguration() , mMaxFocusAreas(0) , mMaxMeteringAreas(0) { MOZ_COUNT_CTOR(nsDOMCameraControl::DOMCameraConfiguration); } nsDOMCameraControl::DOMCameraConfiguration::DOMCameraConfiguration(const CameraConfiguration& aConfiguration) : CameraConfiguration(aConfiguration) , mMaxFocusAreas(0) , mMaxMeteringAreas(0) { MOZ_COUNT_CTOR(nsDOMCameraControl::DOMCameraConfiguration); } nsDOMCameraControl::DOMCameraConfiguration::~DOMCameraConfiguration() { MOZ_COUNT_DTOR(nsDOMCameraControl::DOMCameraConfiguration); } #ifdef MOZ_WIDGET_GONK // This should be long enough for even our slowest platforms. static const unsigned long kCachedCameraTimeoutMs = 3500; // Open the battery-door-facing camera by default. static const uint32_t kDefaultCameraId = 0; /* static */ void nsDOMCameraControl::PreinitCameraHardware() { // Assume a default, minimal configuration. This should initialize the // hardware, but won't (can't) start the preview. nsRefPtr cameraControl = ICameraControl::Create(kDefaultCameraId); if (NS_WARN_IF(!cameraControl)) { return; } sCachedCameraControlStartResult = cameraControl->Start(); if (NS_WARN_IF(NS_FAILED(sCachedCameraControlStartResult))) { return; } sCachedCameraControl = cameraControl; nsCOMPtr timer = do_CreateInstance(NS_TIMER_CONTRACTID); if (NS_WARN_IF(!timer)) { return; } nsresult rv = timer->InitWithFuncCallback(DiscardCachedCameraInstance, nullptr, kCachedCameraTimeoutMs, nsITimer::TYPE_ONE_SHOT); if (NS_WARN_IF(NS_FAILED(rv))) { // If we can't start the timer, it's possible for an app to never grab the // camera, leaving the hardware tied up indefinitely. Better to take the // performance hit. sCachedCameraControl = nullptr; return; } sDiscardCachedCameraControlTimer = timer; } /* static */ void nsDOMCameraControl::DiscardCachedCameraInstance(nsITimer* aTimer, void* aClosure) { MOZ_ASSERT(NS_IsMainThread()); sDiscardCachedCameraControlTimer = nullptr; sCachedCameraControl = nullptr; } #endif nsDOMCameraControl::nsDOMCameraControl(uint32_t aCameraId, const CameraConfiguration& aInitialConfig, Promise* aPromise, nsPIDOMWindow* aWindow) : DOMMediaStream() , mCameraControl(nullptr) , mAudioChannelAgent(nullptr) , mGetCameraPromise(aPromise) , mWindow(aWindow) , mPreviewState(CameraControlListener::kPreviewStopped) , mRecording(false) , mRecordingStoppedDeferred(false) , mSetInitialConfig(false) { DOM_CAMERA_LOGT("%s:%d : this=%p\n", __func__, __LINE__, this); mInput = new CameraPreviewMediaStream(this); BindToOwner(aWindow); nsRefPtr initialConfig = new DOMCameraConfiguration(aInitialConfig); // Create and initialize the underlying camera. ICameraControl::Configuration config; bool haveInitialConfig = false; nsresult rv; switch (aInitialConfig.mMode) { case CameraMode::Picture: config.mMode = ICameraControl::kPictureMode; haveInitialConfig = true; break; case CameraMode::Video: config.mMode = ICameraControl::kVideoMode; haveInitialConfig = true; break; case CameraMode::Unspecified: break; default: MOZ_ASSERT_UNREACHABLE("Unanticipated camera mode!"); break; } if (haveInitialConfig) { 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; gotCached = true; } else { sCachedCameraControl = nullptr; #endif mCameraControl = ICameraControl::Create(aCameraId); #ifdef MOZ_WIDGET_GONK } #endif mCurrentConfiguration = initialConfig.forget(); // Attach our DOM-facing media stream to our viewfinder stream. InitStreamCommon(mInput); MOZ_ASSERT(mWindow, "Shouldn't be created with a null window!"); if (mWindow->GetExtantDoc()) { CombineWithPrincipal(mWindow->GetExtantDoc()->NodePrincipal()); } // Register a listener for camera events. mListener = new DOMCameraControlListener(this, mInput); 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); } } nsDOMCameraControl::~nsDOMCameraControl() { DOM_CAMERA_LOGT("%s:%d : this=%p\n", __func__, __LINE__, this); /*invoke DOMMediaStream destroy*/ Destroy(); } JSObject* nsDOMCameraControl::WrapObject(JSContext* aCx, JS::Handle aGivenProto) { return CameraControlBinding::Wrap(aCx, this, aGivenProto); } 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 >& aValue, uint32_t aLimit) { if (aLimit == 0) { DOM_CAMERA_LOGI("%s:%d : aLimit = 0, nothing to do\n", __func__, __LINE__); return NS_OK; } nsTArray regionArray; if (aValue.WasPassed()) { const Sequence& regions = aValue.Value(); uint32_t length = regions.Length(); DOM_CAMERA_LOGI("%s:%d : got %d regions (limited to %d)\n", __func__, __LINE__, length, aLimit); if (length > aLimit) { length = aLimit; } // aLimit supplied by camera library provides sane ceiling (i.e. <10) regionArray.SetCapacity(length); for (uint32_t i = 0; i < length; ++i) { ICameraControl::Region* r = regionArray.AppendElement(); const CameraRegion ®ion = regions[i]; r->top = region.mTop; r->left = region.mLeft; r->bottom = region.mBottom; r->right = region.mRight; r->weight = region.mWeight; DOM_CAMERA_LOGI("region %d: top=%d, left=%d, bottom=%d, right=%d, weight=%u\n", i, r->top, r->left, r->bottom, r->right, r->weight ); } } else { DOM_CAMERA_LOGI("%s:%d : clear regions\n", __func__, __LINE__); } return mCameraControl->Set(aKey, regionArray); } // Getter for weighted regions: { top, bottom, left, right, weight } nsresult nsDOMCameraControl::Get(uint32_t aKey, nsTArray& aValue) { nsTArray regionArray; nsresult rv = mCameraControl->Get(aKey, regionArray); NS_ENSURE_SUCCESS(rv, rv); uint32_t length = regionArray.Length(); DOM_CAMERA_LOGI("%s:%d : got %d regions\n", __func__, __LINE__, length); aValue.SetLength(length); for (uint32_t i = 0; i < length; ++i) { ICameraControl::Region& r = regionArray[i]; CameraRegion& v = aValue[i]; v.mTop = r.top; v.mLeft = r.left; v.mBottom = r.bottom; v.mRight = r.right; v.mWeight = r.weight; DOM_CAMERA_LOGI("region %d: top=%d, left=%d, bottom=%d, right=%d, weight=%u\n", i, v.mTop, v.mLeft, v.mBottom, v.mRight, v.mWeight ); } return NS_OK; } #define THROW_IF_NO_CAMERACONTROL(...) \ do { \ if (!mCameraControl) { \ DOM_CAMERA_LOGW("mCameraControl is null at %s:%d\n", __func__, __LINE__); \ aRv = NS_ERROR_NOT_AVAILABLE; \ return __VA_ARGS__; \ } \ } while (0) void nsDOMCameraControl::GetEffect(nsString& aEffect, ErrorResult& aRv) { THROW_IF_NO_CAMERACONTROL(); aRv = mCameraControl->Get(CAMERA_PARAM_EFFECT, aEffect); } void nsDOMCameraControl::SetEffect(const nsAString& aEffect, ErrorResult& aRv) { THROW_IF_NO_CAMERACONTROL(); aRv = mCameraControl->Set(CAMERA_PARAM_EFFECT, aEffect); } void nsDOMCameraControl::GetWhiteBalanceMode(nsString& aWhiteBalanceMode, ErrorResult& aRv) { THROW_IF_NO_CAMERACONTROL(); aRv = mCameraControl->Get(CAMERA_PARAM_WHITEBALANCE, aWhiteBalanceMode); } void nsDOMCameraControl::SetWhiteBalanceMode(const nsAString& aWhiteBalanceMode, ErrorResult& aRv) { THROW_IF_NO_CAMERACONTROL(); aRv = mCameraControl->Set(CAMERA_PARAM_WHITEBALANCE, aWhiteBalanceMode); } void nsDOMCameraControl::GetSceneMode(nsString& aSceneMode, ErrorResult& aRv) { THROW_IF_NO_CAMERACONTROL(); aRv = mCameraControl->Get(CAMERA_PARAM_SCENEMODE, aSceneMode); } void nsDOMCameraControl::SetSceneMode(const nsAString& aSceneMode, ErrorResult& aRv) { THROW_IF_NO_CAMERACONTROL(); aRv = mCameraControl->Set(CAMERA_PARAM_SCENEMODE, aSceneMode); } void nsDOMCameraControl::GetFlashMode(nsString& aFlashMode, ErrorResult& aRv) { THROW_IF_NO_CAMERACONTROL(); aRv = mCameraControl->Get(CAMERA_PARAM_FLASHMODE, aFlashMode); } void nsDOMCameraControl::SetFlashMode(const nsAString& aFlashMode, ErrorResult& aRv) { THROW_IF_NO_CAMERACONTROL(); aRv = mCameraControl->Set(CAMERA_PARAM_FLASHMODE, aFlashMode); } void nsDOMCameraControl::GetFocusMode(nsString& aFocusMode, ErrorResult& aRv) { THROW_IF_NO_CAMERACONTROL(); aRv = mCameraControl->Get(CAMERA_PARAM_FOCUSMODE, aFocusMode); } void nsDOMCameraControl::SetFocusMode(const nsAString& aFocusMode, ErrorResult& aRv) { THROW_IF_NO_CAMERACONTROL(); aRv = mCameraControl->Set(CAMERA_PARAM_FOCUSMODE, aFocusMode); } void nsDOMCameraControl::GetIsoMode(nsString& aIsoMode, ErrorResult& aRv) { THROW_IF_NO_CAMERACONTROL(); aRv = mCameraControl->Get(CAMERA_PARAM_ISOMODE, aIsoMode); } void nsDOMCameraControl::SetIsoMode(const nsAString& aIsoMode, ErrorResult& aRv) { THROW_IF_NO_CAMERACONTROL(); aRv = mCameraControl->Set(CAMERA_PARAM_ISOMODE, aIsoMode); } double nsDOMCameraControl::GetPictureQuality(ErrorResult& aRv) { THROW_IF_NO_CAMERACONTROL(1.0); double quality; aRv = mCameraControl->Get(CAMERA_PARAM_PICTURE_QUALITY, quality); return quality; } void nsDOMCameraControl::SetPictureQuality(double aQuality, ErrorResult& aRv) { THROW_IF_NO_CAMERACONTROL(); aRv = mCameraControl->Set(CAMERA_PARAM_PICTURE_QUALITY, aQuality); } void nsDOMCameraControl::GetMeteringMode(nsString& aMode, ErrorResult& aRv) { THROW_IF_NO_CAMERACONTROL(); aRv = mCameraControl->Get(CAMERA_PARAM_METERINGMODE, aMode); } void nsDOMCameraControl::SetMeteringMode(const nsAString& aMode, ErrorResult& aRv) { THROW_IF_NO_CAMERACONTROL(); aRv = mCameraControl->Set(CAMERA_PARAM_METERINGMODE, aMode); } double nsDOMCameraControl::GetZoom(ErrorResult& aRv) { THROW_IF_NO_CAMERACONTROL(1.0); double zoom = 1.0; aRv = mCameraControl->Get(CAMERA_PARAM_ZOOM, zoom); return zoom; } void nsDOMCameraControl::SetZoom(double aZoom, ErrorResult& aRv) { THROW_IF_NO_CAMERACONTROL(); aRv = mCameraControl->Set(CAMERA_PARAM_ZOOM, aZoom); } void nsDOMCameraControl::GetMeteringAreas(nsTArray& aAreas, ErrorResult& aRv) { aRv = Get(CAMERA_PARAM_METERINGAREAS, aAreas); } void nsDOMCameraControl::SetMeteringAreas(const Optional >& aMeteringAreas, ErrorResult& aRv) { aRv = Set(CAMERA_PARAM_METERINGAREAS, aMeteringAreas, mCurrentConfiguration->mMaxMeteringAreas); } void nsDOMCameraControl::GetFocusAreas(nsTArray& aAreas, ErrorResult& aRv) { aRv = Get(CAMERA_PARAM_FOCUSAREAS, aAreas); } void nsDOMCameraControl::SetFocusAreas(const Optional >& aFocusAreas, ErrorResult& aRv) { aRv = Set(CAMERA_PARAM_FOCUSAREAS, aFocusAreas, mCurrentConfiguration->mMaxFocusAreas); } void nsDOMCameraControl::GetPictureSize(CameraSize& aSize, ErrorResult& aRv) { THROW_IF_NO_CAMERACONTROL(); ICameraControl::Size size; aRv = mCameraControl->Get(CAMERA_PARAM_PICTURE_SIZE, size); if (aRv.Failed()) { return; } aSize.mWidth = size.width; aSize.mHeight = size.height; } void nsDOMCameraControl::SetPictureSize(const CameraSize& aSize, ErrorResult& aRv) { THROW_IF_NO_CAMERACONTROL(); ICameraControl::Size s = { aSize.mWidth, aSize.mHeight }; aRv = mCameraControl->Set(CAMERA_PARAM_PICTURE_SIZE, s); } void nsDOMCameraControl::GetThumbnailSize(CameraSize& aSize, ErrorResult& aRv) { THROW_IF_NO_CAMERACONTROL(); ICameraControl::Size size; aRv = mCameraControl->Get(CAMERA_PARAM_THUMBNAILSIZE, size); if (aRv.Failed()) { return; } aSize.mWidth = size.width; aSize.mHeight = size.height; } void nsDOMCameraControl::SetThumbnailSize(const CameraSize& aSize, ErrorResult& aRv) { THROW_IF_NO_CAMERACONTROL(); ICameraControl::Size s = { aSize.mWidth, aSize.mHeight }; aRv = mCameraControl->Set(CAMERA_PARAM_THUMBNAILSIZE, s); } double nsDOMCameraControl::GetFocalLength(ErrorResult& aRv) { THROW_IF_NO_CAMERACONTROL(0.0); double focalLength; aRv = mCameraControl->Get(CAMERA_PARAM_FOCALLENGTH, focalLength); return focalLength; } double nsDOMCameraControl::GetFocusDistanceNear(ErrorResult& aRv) { THROW_IF_NO_CAMERACONTROL(0.0); double distance; aRv = mCameraControl->Get(CAMERA_PARAM_FOCUSDISTANCENEAR, distance); return distance; } double nsDOMCameraControl::GetFocusDistanceOptimum(ErrorResult& aRv) { THROW_IF_NO_CAMERACONTROL(0.0); double distance; aRv = mCameraControl->Get(CAMERA_PARAM_FOCUSDISTANCEOPTIMUM, distance); return distance; } double nsDOMCameraControl::GetFocusDistanceFar(ErrorResult& aRv) { THROW_IF_NO_CAMERACONTROL(0.0); double distance; aRv = mCameraControl->Get(CAMERA_PARAM_FOCUSDISTANCEFAR, distance); return distance; } void nsDOMCameraControl::SetExposureCompensation(double aCompensation, ErrorResult& aRv) { THROW_IF_NO_CAMERACONTROL(); aRv = mCameraControl->Set(CAMERA_PARAM_EXPOSURECOMPENSATION, aCompensation); } double nsDOMCameraControl::GetExposureCompensation(ErrorResult& aRv) { THROW_IF_NO_CAMERACONTROL(0.0); double compensation; aRv = mCameraControl->Get(CAMERA_PARAM_EXPOSURECOMPENSATION, compensation); return compensation; } int32_t nsDOMCameraControl::SensorAngle() { int32_t angle = 0; if (mCameraControl) { mCameraControl->Get(CAMERA_PARAM_SENSORANGLE, angle); } return angle; } already_AddRefed nsDOMCameraControl::Capabilities() { if (!mCameraControl) { DOM_CAMERA_LOGW("mCameraControl is null at %s:%d\n", __func__, __LINE__); return nullptr; } nsRefPtr caps = mCapabilities; if (!caps) { caps = new CameraCapabilities(mWindow, mCameraControl); mCapabilities = caps; } return caps.forget(); } // Methods. already_AddRefed nsDOMCameraControl::StartRecording(const CameraStartRecordingOptions& aOptions, nsDOMDeviceStorage& aStorageArea, const nsAString& aFilename, ErrorResult& aRv) { DOM_CAMERA_LOGT("%s:%d : this=%p\n", __func__, __LINE__, this); nsRefPtr promise = CreatePromise(aRv); if (aRv.Failed()) { return nullptr; } // Must supply both the poster path and storage area or neither if (aOptions.mPosterFilepath.IsEmpty() == static_cast(aOptions.mPosterStorageArea.get())) { promise->MaybeReject(NS_ERROR_ILLEGAL_VALUE); return promise.forget(); } // If we are trying to start recording, already recording or are still // waiting for a poster to be created/fail, we need to wait if (mStartRecordingPromise || mRecording || mRecordingStoppedDeferred || !mOptions.mPosterFilepath.IsEmpty()) { promise->MaybeReject(NS_ERROR_IN_PROGRESS); return promise.forget(); } aRv = NotifyRecordingStatusChange(NS_LITERAL_STRING("starting")); if (aRv.Failed()) { return nullptr; } mDSFileDescriptor = new DeviceStorageFileDescriptor(); nsRefPtr request = aStorageArea.CreateFileDescriptor(aFilename, mDSFileDescriptor.get(), aRv); if (aRv.Failed()) { NotifyRecordingStatusChange(NS_LITERAL_STRING("shutdown")); return nullptr; } nsCOMPtr listener = new StartRecordingHelper(this); aRv = RegisterStorageRequestEvents(request, listener); if (aRv.Failed()) { NotifyRecordingStatusChange(NS_LITERAL_STRING("shutdown")); return nullptr; } mStartRecordingPromise = promise; mOptions = aOptions; mRecording = true; return promise.forget(); } void nsDOMCameraControl::OnCreatedFileDescriptor(bool aSucceeded) { nsresult rv = NS_ERROR_FAILURE; if (!mCameraControl) { rv = NS_ERROR_NOT_AVAILABLE; } else if (!mRecording) { // Race condition where StopRecording comes in before we issue // the start recording request to Gonk rv = NS_ERROR_ABORT; mOptions.mPosterFilepath.Truncate(); mOptions.mPosterStorageArea = nullptr; } else if (aSucceeded && mDSFileDescriptor->mFileDescriptor.IsValid()) { ICameraControl::StartRecordingOptions o; o.rotation = mOptions.mRotation; o.maxFileSizeBytes = mOptions.mMaxFileSizeBytes; o.maxVideoLengthMs = mOptions.mMaxVideoLengthMs; o.autoEnableLowLightTorch = mOptions.mAutoEnableLowLightTorch; o.createPoster = !mOptions.mPosterFilepath.IsEmpty(); rv = mCameraControl->StartRecording(mDSFileDescriptor.get(), &o); if (NS_SUCCEEDED(rv)) { return; } } OnUserError(CameraControlListener::kInStartRecording, rv); if (mDSFileDescriptor->mFileDescriptor.IsValid()) { // An error occured. We need to manually close the file associated with the // FileDescriptor, and we shouldn't do this on the main thread, so we // use a little helper. nsRefPtr closer = new CloseFileRunnable(mDSFileDescriptor->mFileDescriptor); closer->Dispatch(); } } void nsDOMCameraControl::StopRecording(ErrorResult& aRv) { DOM_CAMERA_LOGT("%s:%d : this=%p\n", __func__, __LINE__, this); THROW_IF_NO_CAMERACONTROL(); ReleaseAudioChannelAgent(); mRecording = false; aRv = mCameraControl->StopRecording(); } void nsDOMCameraControl::ResumePreview(ErrorResult& aRv) { DOM_CAMERA_LOGT("%s:%d : this=%p\n", __func__, __LINE__, this); THROW_IF_NO_CAMERACONTROL(); aRv = mCameraControl->StartPreview(); } already_AddRefed nsDOMCameraControl::SetConfiguration(const CameraConfiguration& aConfiguration, ErrorResult& aRv) { DOM_CAMERA_LOGT("%s:%d : this=%p\n", __func__, __LINE__, this); THROW_IF_NO_CAMERACONTROL(nullptr); nsRefPtr promise = CreatePromise(aRv); if (aRv.Failed()) { return nullptr; } 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.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; } mSetConfigurationPromise = promise; return promise.forget(); } already_AddRefed nsDOMCameraControl::AutoFocus(ErrorResult& aRv) { DOM_CAMERA_LOGT("%s:%d : this=%p\n", __func__, __LINE__, this); THROW_IF_NO_CAMERACONTROL(nullptr); nsRefPtr promise = mAutoFocusPromise.forget(); if (promise) { // There is already a call to AutoFocus() in progress, cancel it and // invoke the error callback (if one was passed in). promise->MaybeReject(NS_ERROR_IN_PROGRESS); } promise = CreatePromise(aRv); if (aRv.Failed()) { return nullptr; } aRv = mCameraControl->AutoFocus(); if (aRv.Failed()) { return nullptr; } DispatchStateEvent(NS_LITERAL_STRING("focus"), NS_LITERAL_STRING("focusing")); mAutoFocusPromise = promise; return promise.forget(); } void nsDOMCameraControl::StartFaceDetection(ErrorResult& aRv) { DOM_CAMERA_LOGT("%s:%d : this=%p\n", __func__, __LINE__, this); THROW_IF_NO_CAMERACONTROL(); aRv = mCameraControl->StartFaceDetection(); } void nsDOMCameraControl::StopFaceDetection(ErrorResult& aRv) { DOM_CAMERA_LOGT("%s:%d : this=%p\n", __func__, __LINE__, this); THROW_IF_NO_CAMERACONTROL(); aRv = mCameraControl->StopFaceDetection(); } already_AddRefed nsDOMCameraControl::TakePicture(const CameraPictureOptions& aOptions, ErrorResult& aRv) { DOM_CAMERA_LOGT("%s:%d : this=%p\n", __func__, __LINE__, this); THROW_IF_NO_CAMERACONTROL(nullptr); nsRefPtr promise = CreatePromise(aRv); if (aRv.Failed()) { return nullptr; } if (mTakePicturePromise) { // There is already a call to TakePicture() in progress, abort this new // one and invoke the error callback (if one was passed in). promise->MaybeReject(NS_ERROR_IN_PROGRESS); return promise.forget(); } { ICameraControlParameterSetAutoEnter batch(mCameraControl); // XXXmikeh - remove this: see bug 931155 ICameraControl::Size s; s.width = aOptions.mPictureSize.mWidth; s.height = aOptions.mPictureSize.mHeight; ICameraControl::Position p; p.latitude = aOptions.mPosition.mLatitude; p.longitude = aOptions.mPosition.mLongitude; p.altitude = aOptions.mPosition.mAltitude; p.timestamp = aOptions.mPosition.mTimestamp; if (s.width && s.height) { mCameraControl->Set(CAMERA_PARAM_PICTURE_SIZE, s); } if (!aOptions.mFileFormat.IsEmpty()) { mCameraControl->Set(CAMERA_PARAM_PICTURE_FILEFORMAT, aOptions.mFileFormat); } mCameraControl->Set(CAMERA_PARAM_PICTURE_ROTATION, aOptions.mRotation); mCameraControl->Set(CAMERA_PARAM_PICTURE_DATETIME, aOptions.mDateTime); mCameraControl->SetLocation(p); } aRv = mCameraControl->TakePicture(); if (aRv.Failed()) { return nullptr; } mTakePicturePromise = promise; return promise.forget(); } already_AddRefed nsDOMCameraControl::ReleaseHardware(ErrorResult& aRv) { DOM_CAMERA_LOGI("%s:%d : this=%p\n", __func__, __LINE__, this); nsRefPtr promise = CreatePromise(aRv); if (aRv.Failed()) { return nullptr; } if (!mCameraControl) { // Always succeed if the camera instance is already closed. promise->MaybeResolve(JS::UndefinedHandleValue); return promise.forget(); } aRv = mCameraControl->Stop(); if (aRv.Failed()) { return nullptr; } // Once we stop the camera, there's nothing we can do with it, // so we can throw away this reference. (This won't prevent us // from receiving the last underlying events.) mCameraControl = nullptr; mReleasePromise = promise; return promise.forget(); } void nsDOMCameraControl::ResumeContinuousFocus(ErrorResult& aRv) { DOM_CAMERA_LOGT("%s:%d : this=%p\n", __func__, __LINE__, this); THROW_IF_NO_CAMERACONTROL(); aRv = mCameraControl->ResumeContinuousFocus(); } void nsDOMCameraControl::Shutdown() { DOM_CAMERA_LOGT("%s:%d : this=%p\n", __func__, __LINE__, this); // Remove any pending solicited event handlers; these // reference our window object, which in turn references // us. If we don't remove them, we can leak DOM objects. AbortPromise(mGetCameraPromise); AbortPromise(mAutoFocusPromise); AbortPromise(mTakePicturePromise); AbortPromise(mStartRecordingPromise); AbortPromise(mReleasePromise); AbortPromise(mSetConfigurationPromise); if (mCameraControl) { mCameraControl->Stop(); mCameraControl = nullptr; } } void nsDOMCameraControl::ReleaseAudioChannelAgent() { #ifdef MOZ_B2G if (mAudioChannelAgent) { mAudioChannelAgent->NotifyStoppedPlaying(); mAudioChannelAgent = nullptr; } #endif } nsresult nsDOMCameraControl::NotifyRecordingStatusChange(const nsString& aMsg) { NS_ENSURE_TRUE(mWindow, NS_ERROR_FAILURE); if (aMsg.EqualsLiteral("shutdown")) { ReleaseAudioChannelAgent(); } nsresult rv = MediaManager::NotifyRecordingStatusChange(mWindow, aMsg, true /* aIsAudio */, true /* aIsVideo */); if (NS_FAILED(rv)) { return rv; } #ifdef MOZ_B2G if (aMsg.EqualsLiteral("starting") && !mAudioChannelAgent) { mAudioChannelAgent = do_CreateInstance("@mozilla.org/audiochannelagent;1"); if (!mAudioChannelAgent) { return NS_ERROR_UNEXPECTED; } // Camera app will stop recording when it falls to the background, so no callback is necessary. mAudioChannelAgent->Init(mWindow, (int32_t)AudioChannel::Content, nullptr); // Video recording doesn't output any sound, so it's not necessary to check canPlay. float volume = 0.0; bool muted = true; rv = mAudioChannelAgent->NotifyStartedPlaying(&volume, &muted); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } } #endif return rv; } already_AddRefed nsDOMCameraControl::CreatePromise(ErrorResult& aRv) { nsCOMPtr global = do_QueryInterface(mWindow); if (!global) { aRv.Throw(NS_ERROR_FAILURE); return nullptr; } return Promise::Create(global, aRv); } void nsDOMCameraControl::AbortPromise(nsRefPtr& aPromise) { nsRefPtr promise = aPromise.forget(); if (promise) { promise->MaybeReject(NS_ERROR_NOT_AVAILABLE); } } void nsDOMCameraControl::EventListenerAdded(nsIAtom* aType) { if (aType == nsGkAtoms::onpreviewstatechange) { DispatchPreviewStateEvent(mPreviewState); } } void nsDOMCameraControl::DispatchPreviewStateEvent(CameraControlListener::PreviewState aState) { nsString state; switch (aState) { case CameraControlListener::kPreviewStarted: state = NS_LITERAL_STRING("started"); break; default: state = NS_LITERAL_STRING("stopped"); break; } DispatchStateEvent(NS_LITERAL_STRING("previewstatechange"), state); } void nsDOMCameraControl::DispatchStateEvent(const nsString& aType, const nsString& aState) { CameraStateChangeEventInit eventInit; eventInit.mNewState = aState; nsRefPtr 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 = 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. OnGetCameraComplete(); } break; case CameraControlListener::kHardwareClosed: DOM_CAMERA_LOGI("DOM OnHardwareStateChange: closed\n"); if (!mSetInitialConfig) { nsRefPtr promise = mReleasePromise.forget(); if (promise) { promise->MaybeResolve(JS::UndefinedHandleValue); } CameraClosedEventInit eventInit; switch (aReason) { case NS_OK: eventInit.mReason = NS_LITERAL_STRING("HardwareReleased"); break; case NS_ERROR_FAILURE: eventInit.mReason = NS_LITERAL_STRING("SystemFailure"); break; case NS_ERROR_NOT_AVAILABLE: eventInit.mReason = NS_LITERAL_STRING("NotAvailable"); break; default: DOM_CAMERA_LOGE("Unhandled hardware close reason, 0x%x\n", aReason); MOZ_ASSERT_UNREACHABLE("Unanticipated reason for hardware close"); eventInit.mReason = NS_LITERAL_STRING("SystemFailure"); break; } nsRefPtr 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() { DOM_CAMERA_LOGI("DOM ** SNAP **\n"); MOZ_ASSERT(NS_IsMainThread()); DispatchTrustedEvent(NS_LITERAL_STRING("shutter")); } void nsDOMCameraControl::OnPreviewStateChange(CameraControlListener::PreviewState aState) { DOM_CAMERA_LOGT("%s:%d : this=%p\n", __func__, __LINE__, this); MOZ_ASSERT(NS_IsMainThread()); mPreviewState = aState; nsString state; switch (aState) { case CameraControlListener::kPreviewStarted: state = NS_LITERAL_STRING("started"); break; default: state = NS_LITERAL_STRING("stopped"); break; } DispatchPreviewStateEvent(aState); } void nsDOMCameraControl::OnPoster(BlobImpl* aPoster) { DOM_CAMERA_LOGT("%s:%d : this=%p\n", __func__, __LINE__, this); MOZ_ASSERT(NS_IsMainThread()); MOZ_ASSERT(!mOptions.mPosterFilepath.IsEmpty()); // Destructor will trigger an error notification if any step fails nsRefPtr listener = new RecorderPosterHelper(this); if (NS_WARN_IF(!aPoster)) { return; } nsRefPtr blob = Blob::Create(GetParentObject(), aPoster); if (NS_WARN_IF(!blob)) { return; } if (NS_WARN_IF(!mOptions.mPosterStorageArea)) { return; } ErrorResult rv; nsRefPtr request = mOptions.mPosterStorageArea->AddNamed(blob, mOptions.mPosterFilepath, rv); if (NS_WARN_IF(rv.Failed())) { return; } RegisterStorageRequestEvents(request, listener); } void nsDOMCameraControl::OnRecorderStateChange(CameraControlListener::RecorderState aState, int32_t aArg, int32_t aTrackNum) { // For now, we do nothing with 'aStatus' and 'aTrackNum'. DOM_CAMERA_LOGT("%s:%d : this=%p, state=%u\n", __func__, __LINE__, this, aState); MOZ_ASSERT(NS_IsMainThread()); ErrorResult ignored; nsString state; switch (aState) { case CameraControlListener::kRecorderStarted: { nsRefPtr promise = mStartRecordingPromise.forget(); if (promise) { promise->MaybeResolve(JS::UndefinedHandleValue); } state = NS_LITERAL_STRING("Started"); } break; case CameraControlListener::kRecorderStopped: if (!mOptions.mPosterFilepath.IsEmpty()) { mRecordingStoppedDeferred = true; return; } NotifyRecordingStatusChange(NS_LITERAL_STRING("shutdown")); state = NS_LITERAL_STRING("Stopped"); break; case CameraControlListener::kPosterCreated: state = NS_LITERAL_STRING("PosterCreated"); mOptions.mPosterFilepath.Truncate(); mOptions.mPosterStorageArea = nullptr; break; case CameraControlListener::kPosterFailed: state = NS_LITERAL_STRING("PosterFailed"); mOptions.mPosterFilepath.Truncate(); mOptions.mPosterStorageArea = nullptr; break; #ifdef MOZ_B2G_CAMERA case CameraControlListener::kFileSizeLimitReached: state = NS_LITERAL_STRING("FileSizeLimitReached"); break; case CameraControlListener::kVideoLengthLimitReached: state = NS_LITERAL_STRING("VideoLengthLimitReached"); break; case CameraControlListener::kTrackCompleted: state = NS_LITERAL_STRING("TrackCompleted"); break; case CameraControlListener::kTrackFailed: state = NS_LITERAL_STRING("TrackFailed"); break; case CameraControlListener::kMediaRecorderFailed: state = NS_LITERAL_STRING("MediaRecorderFailed"); break; case CameraControlListener::kMediaServerFailed: state = NS_LITERAL_STRING("MediaServerFailed"); break; #endif default: MOZ_ASSERT_UNREACHABLE("Unanticipated video recorder error"); return; } DispatchStateEvent(NS_LITERAL_STRING("recorderstatechange"), state); if (mRecordingStoppedDeferred && mOptions.mPosterFilepath.IsEmpty()) { mRecordingStoppedDeferred = false; OnRecorderStateChange(CameraControlListener::kRecorderStopped, 0, 0); } } void nsDOMCameraControl::OnConfigurationChange(DOMCameraConfiguration* aConfiguration) { DOM_CAMERA_LOGT("%s:%d : this=%p\n", __func__, __LINE__, this); MOZ_ASSERT(NS_IsMainThread()); MOZ_ASSERT(aConfiguration != nullptr); // Update our record of the current camera configuration mCurrentConfiguration = aConfiguration; DOM_CAMERA_LOGI("DOM OnConfigurationChange: this=%p\n", this); 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 = mSetConfigurationPromise.forget(); if (promise) { promise->MaybeResolve(*aConfiguration); } CameraConfigurationEventInit eventInit; eventInit.mMode = mCurrentConfiguration->mMode; eventInit.mRecorderProfile = mCurrentConfiguration->mRecorderProfile; eventInit.mPreviewSize = new DOMRect(static_cast(this), 0, 0, mCurrentConfiguration->mPreviewSize.mWidth, mCurrentConfiguration->mPreviewSize.mHeight); eventInit.mPictureSize = new DOMRect(static_cast(this), 0, 0, mCurrentConfiguration->mPictureSize.mWidth, mCurrentConfiguration->mPictureSize.mHeight); nsRefPtr event = CameraConfigurationEvent::Constructor(this, NS_LITERAL_STRING("configurationchanged"), eventInit); DispatchTrustedEvent(event); } void nsDOMCameraControl::OnAutoFocusComplete(bool aAutoFocusSucceeded) { DOM_CAMERA_LOGT("%s:%d : this=%p\n", __func__, __LINE__, this); MOZ_ASSERT(NS_IsMainThread()); nsRefPtr promise = mAutoFocusPromise.forget(); if (promise) { promise->MaybeResolve(aAutoFocusSucceeded); } if (aAutoFocusSucceeded) { DispatchStateEvent(NS_LITERAL_STRING("focus"), NS_LITERAL_STRING("focused")); } else { DispatchStateEvent(NS_LITERAL_STRING("focus"), NS_LITERAL_STRING("unfocused")); } } void nsDOMCameraControl::OnAutoFocusMoving(bool aIsMoving) { DOM_CAMERA_LOGT("%s:%d : this=%p\n", __func__, __LINE__, this); MOZ_ASSERT(NS_IsMainThread()); if (aIsMoving) { DispatchStateEvent(NS_LITERAL_STRING("focus"), NS_LITERAL_STRING("focusing")); } } void nsDOMCameraControl::OnFacesDetected(const nsTArray& aFaces) { DOM_CAMERA_LOGI("DOM OnFacesDetected %zu face(s)\n", aFaces.Length()); MOZ_ASSERT(NS_IsMainThread()); Sequence > faces; uint32_t len = aFaces.Length(); if (faces.SetCapacity(len, fallible)) { for (uint32_t i = 0; i < len; ++i) { *faces.AppendElement(fallible) = new DOMCameraDetectedFace(static_cast(this), aFaces[i]); } } CameraFacesDetectedEventInit eventInit; eventInit.mFaces.SetValue(faces); nsRefPtr event = CameraFacesDetectedEvent::Constructor(this, NS_LITERAL_STRING("facesdetected"), eventInit); DispatchTrustedEvent(event); } void nsDOMCameraControl::OnTakePictureComplete(nsIDOMBlob* aPicture) { DOM_CAMERA_LOGT("%s:%d : this=%p\n", __func__, __LINE__, this); MOZ_ASSERT(NS_IsMainThread()); MOZ_ASSERT(aPicture); nsRefPtr promise = mTakePicturePromise.forget(); if (promise) { nsCOMPtr picture = aPicture; promise->MaybeResolve(picture); } nsRefPtr blob = static_cast(aPicture); BlobEventInit eventInit; eventInit.mData = blob; nsRefPtr event = BlobEvent::Constructor(this, NS_LITERAL_STRING("picture"), eventInit); DispatchTrustedEvent(event); } void nsDOMCameraControl::OnUserError(CameraControlListener::UserContext aContext, nsresult aError) { DOM_CAMERA_LOGI("DOM OnUserError : this=%p, aContext=%u, aError=0x%x\n", this, aContext, aError); MOZ_ASSERT(NS_IsMainThread()); nsRefPtr promise; switch (aContext) { case CameraControlListener::kInStartCamera: promise = mGetCameraPromise.forget(); // If we failed to open the camera, we never actually provided a reference // for the application to release explicitly. Thus we must clear our handle // here to ensure everything is freed. mCameraControl = nullptr; break; case CameraControlListener::kInStopCamera: promise = mReleasePromise.forget(); if (aError == NS_ERROR_NOT_INITIALIZED) { // This value indicates that the hardware is already closed; which for // kInStopCamera, is not actually an error. if (promise) { promise->MaybeResolve(JS::UndefinedHandleValue); } return; } break; case CameraControlListener::kInSetConfiguration: if (mSetInitialConfig && mCameraControl) { // 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; case CameraControlListener::kInTakePicture: promise = mTakePicturePromise.forget(); break; case CameraControlListener::kInStartRecording: promise = mStartRecordingPromise.forget(); mRecording = false; NotifyRecordingStatusChange(NS_LITERAL_STRING("shutdown")); break; case CameraControlListener::kInStartFaceDetection: // This method doesn't have any callbacks, so all we can do is log the // failure. This only happens after the hardware has been released. NS_WARNING("Failed to start face detection"); return; case CameraControlListener::kInStopFaceDetection: // This method doesn't have any callbacks, so all we can do is log the // failure. This only happens after the hardware has been released. NS_WARNING("Failed to stop face detection"); return; case CameraControlListener::kInResumeContinuousFocus: // This method doesn't have any callbacks, so all we can do is log the // failure. This only happens after the hardware has been released. NS_WARNING("Failed to resume continuous focus"); return; case CameraControlListener::kInStopRecording: // This method doesn't have any callbacks, so all we can do is log the // failure. This only happens after the hardware has been released. NS_WARNING("Failed to stop recording"); return; case CameraControlListener::kInStartPreview: // This method doesn't have any callbacks, so all we can do is log the // failure. This only happens after the hardware has been released. NS_WARNING("Failed to (re)start preview"); return; case CameraControlListener::kInStopPreview: // This method doesn't have any callbacks, so all we can do is log the // failure. This only happens after the hardware has been released. NS_WARNING("Failed to stop preview"); return; case CameraControlListener::kInSetPictureSize: // This method doesn't have any callbacks, so all we can do is log the // failure. This only happens after the hardware has been released. NS_WARNING("Failed to set picture size"); return; case CameraControlListener::kInSetThumbnailSize: // This method doesn't have any callbacks, so all we can do is log the // failure. This only happens after the hardware has been released. NS_WARNING("Failed to set thumbnail size"); return; default: { nsPrintfCString msg("Unhandled aContext=%u, aError=0x%x\n", aContext, aError); NS_WARNING(msg.get()); } MOZ_ASSERT_UNREACHABLE("Unhandled user error"); return; } if (!promise) { DOM_CAMERA_LOGW("DOM No error handler for aError=0x%x in aContext=%u\n", aError, aContext); return; } promise->MaybeReject(aError); }