/* 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 "base/basictypes.h" #include "nsCOMPtr.h" #include "nsDOMClassInfo.h" #include "nsHashPropertyBag.h" #include "nsThread.h" #include "DeviceStorage.h" #include "mozilla/dom/CameraControlBinding.h" #include "mozilla/dom/TabChild.h" #include "mozilla/Services.h" #include "mozilla/unused.h" #include "nsIAppsService.h" #include "nsIObserverService.h" #include "nsIDOMDeviceStorage.h" #include "nsIScriptSecurityManager.h" #include "nsXULAppAPI.h" #include "DOMCameraManager.h" #include "DOMCameraCapabilities.h" #include "DOMCameraControl.h" #include "CameraCommon.h" #include "mozilla/dom/CameraManagerBinding.h" #include "mozilla/dom/BindingUtils.h" using namespace mozilla; using namespace mozilla::dom; using namespace mozilla::idl; NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_2(nsDOMCameraControl, mDOMCapabilities, mWindow) NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsDOMCameraControl) NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY NS_INTERFACE_MAP_ENTRY(nsISupports) NS_INTERFACE_MAP_END NS_IMPL_CYCLE_COLLECTING_ADDREF(nsDOMCameraControl) NS_IMPL_CYCLE_COLLECTING_RELEASE(nsDOMCameraControl) nsDOMCameraControl::~nsDOMCameraControl() { DOM_CAMERA_LOGT("%s:%d : this=%p\n", __func__, __LINE__, this); } JSObject* nsDOMCameraControl::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aScope) { return CameraControlBinding::Wrap(aCx, aScope, this); } nsICameraCapabilities* nsDOMCameraControl::Capabilities() { if (!mDOMCapabilities) { mDOMCapabilities = new DOMCameraCapabilities(mCameraControl); } return mDOMCapabilities; } void nsDOMCameraControl::GetEffect(nsString& aEffect, ErrorResult& aRv) { aRv = mCameraControl->Get(CAMERA_PARAM_EFFECT, aEffect); } void nsDOMCameraControl::SetEffect(const nsAString& aEffect, ErrorResult& aRv) { aRv = mCameraControl->Set(CAMERA_PARAM_EFFECT, aEffect); } void nsDOMCameraControl::GetWhiteBalanceMode(nsString& aWhiteBalanceMode, ErrorResult& aRv) { aRv = mCameraControl->Get(CAMERA_PARAM_WHITEBALANCE, aWhiteBalanceMode); } void nsDOMCameraControl::SetWhiteBalanceMode(const nsAString& aWhiteBalanceMode, ErrorResult& aRv) { aRv = mCameraControl->Set(CAMERA_PARAM_WHITEBALANCE, aWhiteBalanceMode); } void nsDOMCameraControl::GetSceneMode(nsString& aSceneMode, ErrorResult& aRv) { aRv = mCameraControl->Get(CAMERA_PARAM_SCENEMODE, aSceneMode); } void nsDOMCameraControl::SetSceneMode(const nsAString& aSceneMode, ErrorResult& aRv) { aRv = mCameraControl->Set(CAMERA_PARAM_SCENEMODE, aSceneMode); } void nsDOMCameraControl::GetFlashMode(nsString& aFlashMode, ErrorResult& aRv) { aRv = mCameraControl->Get(CAMERA_PARAM_FLASHMODE, aFlashMode); } void nsDOMCameraControl::SetFlashMode(const nsAString& aFlashMode, ErrorResult& aRv) { aRv = mCameraControl->Set(CAMERA_PARAM_FLASHMODE, aFlashMode); } void nsDOMCameraControl::GetFocusMode(nsString& aFocusMode, ErrorResult& aRv) { aRv = mCameraControl->Get(CAMERA_PARAM_FOCUSMODE, aFocusMode); } void nsDOMCameraControl::SetFocusMode(const nsAString& aFocusMode, ErrorResult& aRv) { aRv = mCameraControl->Set(CAMERA_PARAM_FOCUSMODE, aFocusMode); } double nsDOMCameraControl::GetZoom(ErrorResult& aRv) { double zoom; aRv = mCameraControl->Get(CAMERA_PARAM_ZOOM, &zoom); return zoom; } void nsDOMCameraControl::SetZoom(double aZoom, ErrorResult& aRv) { aRv = mCameraControl->Set(CAMERA_PARAM_ZOOM, aZoom); } /* attribute jsval meteringAreas; */ JS::Value nsDOMCameraControl::GetMeteringAreas(JSContext* cx, ErrorResult& aRv) { JS::Rooted<JS::Value> areas(cx); aRv = mCameraControl->Get(cx, CAMERA_PARAM_METERINGAREAS, areas.address()); return areas; } void nsDOMCameraControl::SetMeteringAreas(JSContext* cx, JS::Handle<JS::Value> aMeteringAreas, ErrorResult& aRv) { aRv = mCameraControl->SetMeteringAreas(cx, aMeteringAreas); } JS::Value nsDOMCameraControl::GetFocusAreas(JSContext* cx, ErrorResult& aRv) { JS::Rooted<JS::Value> value(cx); aRv = mCameraControl->Get(cx, CAMERA_PARAM_FOCUSAREAS, value.address()); return value; } void nsDOMCameraControl::SetFocusAreas(JSContext* cx, JS::Handle<JS::Value> aFocusAreas, ErrorResult& aRv) { aRv = mCameraControl->SetFocusAreas(cx, aFocusAreas); } static nsresult GetSize(JSContext* aCx, JS::Value* aValue, const CameraSize& aSize) { JS::Rooted<JSObject*> o(aCx, JS_NewObject(aCx, nullptr, nullptr, nullptr)); if (!o) { return NS_ERROR_OUT_OF_MEMORY; } JS::Rooted<JS::Value> v(aCx); v = INT_TO_JSVAL(aSize.width); if (!JS_SetProperty(aCx, o, "width", v)) { return NS_ERROR_FAILURE; } v = INT_TO_JSVAL(aSize.height); if (!JS_SetProperty(aCx, o, "height", v)) { return NS_ERROR_FAILURE; } *aValue = JS::ObjectValue(*o); return NS_OK; } /* attribute any pictureSize */ JS::Value nsDOMCameraControl::GetPictureSize(JSContext* cx, ErrorResult& aRv) { JS::Rooted<JS::Value> value(cx); CameraSize size; aRv = mCameraControl->Get(CAMERA_PARAM_PICTURESIZE, size); if (aRv.Failed()) { return value; } aRv = GetSize(cx, value.address(), size); return value; } void nsDOMCameraControl::SetPictureSize(JSContext* cx, JS::Handle<JS::Value> aSize, ErrorResult& aRv) { CameraSize size; aRv = size.Init(cx, aSize.address()); if (aRv.Failed()) { return; } aRv = mCameraControl->Set(CAMERA_PARAM_PICTURESIZE, size); } /* attribute any thumbnailSize */ JS::Value nsDOMCameraControl::GetThumbnailSize(JSContext* cx, ErrorResult& aRv) { JS::Rooted<JS::Value> value(cx); CameraSize size; aRv = mCameraControl->Get(CAMERA_PARAM_THUMBNAILSIZE, size); if (aRv.Failed()) { return value; } aRv = GetSize(cx, value.address(), size); return value; } void nsDOMCameraControl::SetThumbnailSize(JSContext* cx, JS::Handle<JS::Value> aSize, ErrorResult& aRv) { CameraSize size; aRv = size.Init(cx, aSize.address()); if (aRv.Failed()) { return; } aRv = mCameraControl->Set(CAMERA_PARAM_THUMBNAILSIZE, size); } double nsDOMCameraControl::GetFocalLength(ErrorResult& aRv) { double focalLength; aRv = mCameraControl->Get(CAMERA_PARAM_FOCALLENGTH, &focalLength); return focalLength; } double nsDOMCameraControl::GetFocusDistanceNear(ErrorResult& aRv) { double distance; aRv = mCameraControl->Get(CAMERA_PARAM_FOCUSDISTANCENEAR, &distance); return distance; } double nsDOMCameraControl::GetFocusDistanceOptimum(ErrorResult& aRv) { double distance; aRv = mCameraControl->Get(CAMERA_PARAM_FOCUSDISTANCEOPTIMUM, &distance); return distance; } double nsDOMCameraControl::GetFocusDistanceFar(ErrorResult& aRv) { double distance; aRv = mCameraControl->Get(CAMERA_PARAM_FOCUSDISTANCEFAR, &distance); return distance; } void nsDOMCameraControl::SetExposureCompensation(const Optional<double>& aCompensation, ErrorResult& aRv) { if (!aCompensation.WasPassed()) { // use NaN to switch the camera back into auto mode aRv = mCameraControl->Set(CAMERA_PARAM_EXPOSURECOMPENSATION, NAN); } aRv = mCameraControl->Set(CAMERA_PARAM_EXPOSURECOMPENSATION, aCompensation.Value()); } double nsDOMCameraControl::GetExposureCompensation(ErrorResult& aRv) { double compensation; aRv = mCameraControl->Get(CAMERA_PARAM_EXPOSURECOMPENSATION, &compensation); return compensation; } already_AddRefed<nsICameraShutterCallback> nsDOMCameraControl::GetOnShutter(ErrorResult& aRv) { nsCOMPtr<nsICameraShutterCallback> cb; aRv = mCameraControl->Get(getter_AddRefs(cb)); return cb.forget(); } void nsDOMCameraControl::SetOnShutter(nsICameraShutterCallback* aOnShutter, ErrorResult& aRv) { aRv = mCameraControl->Set(aOnShutter); } /* attribute nsICameraClosedCallback onClosed; */ already_AddRefed<nsICameraClosedCallback> nsDOMCameraControl::GetOnClosed(ErrorResult& aRv) { nsCOMPtr<nsICameraClosedCallback> onClosed; aRv = mCameraControl->Get(getter_AddRefs(onClosed)); return onClosed.forget(); } void nsDOMCameraControl::SetOnClosed(nsICameraClosedCallback* aOnClosed, ErrorResult& aRv) { aRv = mCameraControl->Set(aOnClosed); } already_AddRefed<nsICameraRecorderStateChange> nsDOMCameraControl::GetOnRecorderStateChange(ErrorResult& aRv) { nsCOMPtr<nsICameraRecorderStateChange> cb; aRv = mCameraControl->Get(getter_AddRefs(cb)); return cb.forget(); } void nsDOMCameraControl::SetOnRecorderStateChange(nsICameraRecorderStateChange* aOnRecorderStateChange, ErrorResult& aRv) { aRv = mCameraControl->Set(aOnRecorderStateChange); } void nsDOMCameraControl::StartRecording(JSContext* aCx, JS::Handle<JS::Value> aOptions, nsDOMDeviceStorage& storageArea, const nsAString& filename, nsICameraStartRecordingCallback* onSuccess, const Optional<nsICameraErrorCallback*>& onError, ErrorResult& aRv) { MOZ_ASSERT(onSuccess, "no onSuccess handler passed"); mozilla::idl::CameraStartRecordingOptions options; // Default values, until the dictionary parser can handle them. options.rotation = 0; options.maxFileSizeBytes = 0; options.maxVideoLengthMs = 0; aRv = options.Init(aCx, aOptions.address()); if (aRv.Failed()) { return; } nsCOMPtr<nsIObserverService> obs = services::GetObserverService(); if (!obs) { NS_WARNING("Could not get the Observer service for CameraControl::StartRecording."); aRv.Throw(NS_ERROR_FAILURE); return; } nsRefPtr<nsHashPropertyBag> props = CreateRecordingDeviceEventsSubject(); obs->NotifyObservers(static_cast<nsIPropertyBag2*>(props), "recording-device-events", NS_LITERAL_STRING("starting").get()); // Forward recording events to parent process. // The events are gathered in chrome process and used for recording indicator if (XRE_GetProcessType() != GeckoProcessType_Default) { unused << TabChild::GetFrom(mWindow)->SendRecordingDeviceEvents(NS_LITERAL_STRING("starting"), true /* isAudio */, true /* isVideo */); } #ifdef MOZ_B2G if (!mAudioChannelAgent) { mAudioChannelAgent = do_CreateInstance("@mozilla.org/audiochannelagent;1"); if (mAudioChannelAgent) { // Camera app will stop recording when it falls to the background, so no callback is necessary. mAudioChannelAgent->Init(AUDIO_CHANNEL_CONTENT, nullptr); // Video recording doesn't output any sound, so it's not necessary to check canPlay. int32_t canPlay; mAudioChannelAgent->StartPlaying(&canPlay); } } #endif nsCOMPtr<nsIFile> folder; aRv = storageArea.GetRootDirectoryForFile(filename, getter_AddRefs(folder)); if (aRv.Failed()) { return; } aRv = mCameraControl->StartRecording(&options, folder, filename, onSuccess, onError.WasPassed() ? onError.Value() : nullptr); } void nsDOMCameraControl::StopRecording(ErrorResult& aRv) { nsCOMPtr<nsIObserverService> obs = services::GetObserverService(); if (!obs) { NS_WARNING("Could not get the Observer service for CameraControl::StopRecording."); aRv.Throw(NS_ERROR_FAILURE); } nsRefPtr<nsHashPropertyBag> props = CreateRecordingDeviceEventsSubject(); obs->NotifyObservers(static_cast<nsIPropertyBag2*>(props) , "recording-device-events", NS_LITERAL_STRING("shutdown").get()); // Forward recording events to parent process. // The events are gathered in chrome process and used for recording indicator if (XRE_GetProcessType() != GeckoProcessType_Default) { unused << TabChild::GetFrom(mWindow)->SendRecordingDeviceEvents(NS_LITERAL_STRING("shutdown"), true /* isAudio */, true /* isVideo */); } #ifdef MOZ_B2G if (mAudioChannelAgent) { mAudioChannelAgent->StopPlaying(); mAudioChannelAgent = nullptr; } #endif aRv = mCameraControl->StopRecording(); } void nsDOMCameraControl::GetPreviewStream(JSContext* aCx, JS::Handle<JS::Value> aOptions, nsICameraPreviewStreamCallback* onSuccess, const Optional<nsICameraErrorCallback*>& onError, ErrorResult& aRv) { mozilla::idl::CameraSize size; aRv = size.Init(aCx, aOptions.address()); if (aRv.Failed()) { return; } aRv = mCameraControl->GetPreviewStream(size, onSuccess, onError.WasPassed() ? onError.Value() : nullptr); } void nsDOMCameraControl::ResumePreview(ErrorResult& aRv) { aRv = mCameraControl->StartPreview(nullptr); } already_AddRefed<nsICameraPreviewStateChange> nsDOMCameraControl::GetOnPreviewStateChange() const { nsCOMPtr<nsICameraPreviewStateChange> cb; mCameraControl->Get(getter_AddRefs(cb)); return cb.forget(); } void nsDOMCameraControl::SetOnPreviewStateChange(nsICameraPreviewStateChange* aCb) { mCameraControl->Set(aCb); } void nsDOMCameraControl::AutoFocus(nsICameraAutoFocusCallback* onSuccess, const Optional<nsICameraErrorCallback*>& onError, ErrorResult& aRv) { aRv = mCameraControl->AutoFocus(onSuccess, onError.WasPassed() ? onError.Value() : nullptr); } void nsDOMCameraControl::TakePicture(JSContext* aCx, const CameraPictureOptions& aOptions, nsICameraTakePictureCallback* onSuccess, const Optional<nsICameraErrorCallback*>& aOnError, ErrorResult& aRv) { mozilla::idl::CameraSize size; mozilla::idl::CameraPosition pos; aRv = size.Init(aCx, &aOptions.mPictureSize); if (aRv.Failed()) { return; } /** * Default values, until the dictionary parser can handle them. * NaN indicates no value provided. */ pos.latitude = NAN; pos.longitude = NAN; pos.altitude = NAN; pos.timestamp = NAN; aRv = pos.Init(aCx, &aOptions.mPosition); if (aRv.Failed()) { return; } nsICameraErrorCallback* onError = aOnError.WasPassed() ? aOnError.Value() : nullptr; aRv = mCameraControl->TakePicture(size, aOptions.mRotation, aOptions.mFileFormat, pos, aOptions.mDateTime, onSuccess, onError); } void nsDOMCameraControl::GetPreviewStreamVideoMode(JSContext* aCx, JS::Handle<JS::Value> aOptions, nsICameraPreviewStreamCallback* onSuccess, const Optional<nsICameraErrorCallback*>& onError, ErrorResult& aRv) { mozilla::idl::CameraRecorderOptions options; aRv = options.Init(aCx, aOptions.address()); if (aRv.Failed()) { return; } aRv = mCameraControl->GetPreviewStreamVideoMode(&options, onSuccess, onError.WasPassed() ? onError.Value() : nullptr); } void nsDOMCameraControl::ReleaseHardware(const Optional<nsICameraReleaseCallback*>& onSuccess, const Optional<nsICameraErrorCallback*>& onError, ErrorResult& aRv) { aRv = mCameraControl->ReleaseHardware( onSuccess.WasPassed() ? onSuccess.Value() : nullptr, onError.WasPassed() ? onError.Value() : nullptr); } class GetCameraResult : public nsRunnable { public: GetCameraResult(nsDOMCameraControl* aDOMCameraControl, nsresult aResult, const nsMainThreadPtrHandle<nsICameraGetCameraCallback>& onSuccess, const nsMainThreadPtrHandle<nsICameraErrorCallback>& onError, uint64_t aWindowId) : mDOMCameraControl(aDOMCameraControl) , mResult(aResult) , mOnSuccessCb(onSuccess) , mOnErrorCb(onError) , mWindowId(aWindowId) { } NS_IMETHOD Run() { MOZ_ASSERT(NS_IsMainThread()); if (nsDOMCameraManager::IsWindowStillActive(mWindowId)) { DOM_CAMERA_LOGT("%s : this=%p -- BEFORE CALLBACK\n", __func__, this); if (NS_FAILED(mResult)) { if (mOnErrorCb.get()) { mOnErrorCb->HandleEvent(NS_LITERAL_STRING("FAILURE")); } } else { if (mOnSuccessCb.get()) { mOnSuccessCb->HandleEvent(mDOMCameraControl); } } DOM_CAMERA_LOGT("%s : this=%p -- AFTER CALLBACK\n", __func__, this); } /** * Finally, release the extra reference to the DOM-facing camera control. * See the nsDOMCameraControl constructor for the corresponding call to * NS_ADDREF_THIS(). */ NS_RELEASE(mDOMCameraControl); return NS_OK; } protected: /** * 'mDOMCameraControl' is a raw pointer to a previously ADDREF()ed object, * which is released in Run(). */ nsDOMCameraControl* mDOMCameraControl; nsresult mResult; nsMainThreadPtrHandle<nsICameraGetCameraCallback> mOnSuccessCb; nsMainThreadPtrHandle<nsICameraErrorCallback> mOnErrorCb; uint64_t mWindowId; }; nsresult nsDOMCameraControl::Result(nsresult aResult, const nsMainThreadPtrHandle<nsICameraGetCameraCallback>& onSuccess, const nsMainThreadPtrHandle<nsICameraErrorCallback>& onError, uint64_t aWindowId) { nsCOMPtr<GetCameraResult> getCameraResult = new GetCameraResult(this, aResult, onSuccess, onError, aWindowId); return NS_DispatchToMainThread(getCameraResult); } void nsDOMCameraControl::Shutdown() { DOM_CAMERA_LOGI("%s:%d\n", __func__, __LINE__); mCameraControl->Shutdown(); } nsRefPtr<ICameraControl> nsDOMCameraControl::GetNativeCameraControl() { return mCameraControl; } already_AddRefed<nsHashPropertyBag> nsDOMCameraControl::CreateRecordingDeviceEventsSubject() { MOZ_ASSERT(mWindow); nsRefPtr<nsHashPropertyBag> props = new nsHashPropertyBag(); props->SetPropertyAsBool(NS_LITERAL_STRING("isAudio"), true); props->SetPropertyAsBool(NS_LITERAL_STRING("isVideo"), true); nsCOMPtr<nsIDocShell> docShell = mWindow->GetDocShell(); if (docShell) { bool isApp; DebugOnly<nsresult> rv = docShell->GetIsApp(&isApp); MOZ_ASSERT(NS_SUCCEEDED(rv)); nsString requestURL; if (isApp) { rv = docShell->GetAppManifestURL(requestURL); MOZ_ASSERT(NS_SUCCEEDED(rv)); } else { nsCString pageURL; nsCOMPtr<nsIURI> docURI = mWindow->GetDocumentURI(); MOZ_ASSERT(docURI); rv = docURI->GetSpec(pageURL); MOZ_ASSERT(NS_SUCCEEDED(rv)); requestURL = NS_ConvertUTF8toUTF16(pageURL); } props->SetPropertyAsBool(NS_LITERAL_STRING("isApp"), isApp); props->SetPropertyAsAString(NS_LITERAL_STRING("requestURL"), requestURL); } return props.forget(); }