Files
palemoon27/dom/camera/DOMCameraControl.cpp
T
roytam1 bd9cfd0d73 import changes from `dev' branch of rmottola/Arctic-Fox:
- Bug 1148708: Add missing 'override' annotations in DocAccessibleChild.h. rs=ehsan (d606358545)
- Bug 1210408 - make nsMaiInterfaceAction work with proxies, r=tbsaunde (f7c819c6ae)
- Bug 1210407 - teach nsMaiInterfaceTable to use proxies, r=tbsaunde (4ca4f10b5f)
- bug 1185157 make sure we don't send an event to a destroyed ipc document r=billm (23acf53f75)
- bug 1214864 - make SetCarretOffset() async r=davidb (e3079e9b2d)
- missing of Bug 1139972 - IPC Proxy for charAt, r=tbsaunde (e9593ed752)
- bug 1191598 - Pass MOZ_CURRENT_PROJECT in environment when running post-build automation steps for universal mac builds. r=gps (fc342c6ced)
- Bug 1164596 - Add mach android-emulator command; r=ahal (afeb9b27d1)
- Bug 1223149 - Add basic usage documentation for mach build; r=glandium (bfb802d175)
- Bug 1182301 - Improve 'mach build' notifications. r=gps (2c65a122d1)
- Bug 1184696 - Add clobber targets to |mach clobber|; Ability to clobber compiled python files, r=gps (35d8be292e)
- Bug 1117958 - Allow any debugging options to the run or gtest mach subcommands to automatically enable debugging. r=gps (32f986af4b)
- Bug 1180081 - Properly rebuild gtest/libxul before running gtests. r=gps (80db9a3d49)
- Bug 1171647, part 1 - Define a new function to convert the mode to a string. r=njn (61ad16f5ba)
- Bug 1171647, part 2 - Remove redundant assertion for dark matter mode. r=njn (b5ac9519f3)
- Bug 1058178, part 1 - Implement DMD heap scanning mode. r=njn (60e1079536)
- Bug 1058178, part 2 - Implement address clamping analysis for DMD scan logs. r=njn (45c0326b93)
- Bug 1102388 - Fix DMD static constructor ordering dependency. r=mccr8 (59b87897a1)
- Bug 1128705 - Don't redefine PAGE_SIZE in DMD if it's already defined. r=erahm (49216348ee)
- Bug 1179042 - Add a script for analyzing memory blocks using a heap scan DMD log. r=njn DONTBUILD (1c08d2d66e)
- Bug 1207245 - part 6 - rename nsRefPtr<T> to RefPtr<T>; r=ehsan; a=Tomcat (1c999d139b)
- Bug 1158772 - fix non-idiomatic memset call in nsDeque.cpp; r=erahm (de6b555245)
- Bug 1199400 - Part 1: Use CheckedInt when growing nsDeque capacity. r=froydnj (dfdf6814a3)
- Bug 1199400 - Part 0: Remove unused nsDequeIterator. r=froydnj (38d69d7f47)
- Bug 1199400 - Part 2: Add tests for possible nsDeque corner cases. r=froydnj (931baff195)
- Bug 1201997 - Part 1 - Converted compiled test to gtest for nsDeque class. Added tests to test untested methods. r=froydn (e893916651)
- Bug 1201997 - Part 2 - Removing unused methods from the nsDeque class. r=froydn (41595a90ac)
- Bug 1201997 - Part 3 - Make internally used methods private. r=froydn (8cd3afd96f)
- Bug 1201997 - Part 4 - Change size and offset variables to size_t.r=froydn (73eabc8d60)
- Bug 1215140 P1 Add an nsIConsoleReportCollector interface to support navigation channel logging. r=bz (8a41535e2b)
- Bug 1215140 P2 Make HttpBaseChannel implement nsIConsoleReportCollector. r=bz (75fca301f2)
- Bug 1197679 - If nsUnknownDecoder is involved in e10s DivertToParent can break. r=jduell (5d94a12504)
- Bug 1178991 - smartptr for http converter r=hurley (8e7fbc8443)
- bug 366559 - patch 2, fix nsHTTPCompressConv indentation r=bagder (ba762da587)
- bug 366559 - patch 3, fix nsHTTPCompressConv bracing style r=bagder (54195ab451)
- bits of  bug 366559 - patch 7, content-encoding brotli for http (f0b4051022)
- Bug 1205112 - Make PushEvent.data nullable. r=mt,smaug (775db32856)
- Bug 1193414 - SharedWorkers thread should be kept alive also when the SharedWorker object is CCed, r=khuey (b77ea8125c)
- Bug 1206520: Add about:config prefs to enable throwing on asm.js validation failures; r=bz (c42126665d)
- Bug 1193414 - Telemetry for SharedWorker spawning. r=bkelly (77984b7bcc)
- Bug 1205676 - Enable WPT service-worker/unregister-then-register-new-script.https.html in e10s, r=nsm (ec24939cf6)
- Bug 1193133 - Throw when calling postMessage from a Service Worker dom object with no global. r=bkelly (526dcacfab)
- Bug 1181871 P1 Only enforce Cache Context shared data destruction on target thread after init. r=ehsan (cdbf3ed3a8)
- Bug 1181871 P2 Fix ServiceWorkerManager usage of stack-based ErrorResult. r=ehsan (c449195d90)
- minor cleanup and missing bit of 1198230 (02f459db05)
- Bug 1143717 - Implement the ServiceWorkerMessageEvent interface. r=baku (027b3465f2)
- fix misspatch (708eee4e84)
- Bug 1188545 - Disentangle service workers from shared workers and refactor event dispatching code into a separate class. r=nsm,mrbkap (fb5b5341c9)
- Bug 1205228 - Change PackagedAppVerifier to notify the verification result asynchronously. r=valentin. (9edda0fa00)
- Bug 1178518 - Packaged App Utils. r=valentin (f60f3b7a93)
- Bug 1213150 - Part 1: Add a nsContentUtils::IsNonSubresourceRequest helper; r=jdm (b509cc3cc9)
- Bug 1213150 - Part 2: Rework ShouldPrepareForIntercept() in terms of subresource requests; r=jdm (2e92fe8780)
- Bug 1213150 - Part 3: Remove nsIInterceptedChannel.isNavigation; r=jdm (becf1cc12f)
- Bug 1213150 follow-up: fix build bustage (8d73d6ca73)
- Bug 1198394 - Part 1: Allow interception of HSTS upgraded connections in non-e10s mode; r=mcmanus (f504c5be08)
- Bug 1198394 - Part 2: Add a test for interception of HSTS upgraded connections; r=jdm (054e984eef)
- Bug 1187011 - Don't allow response body with null body status. r=bkelly (b1860741d1)
- missing bit of 1140788 (29d319712e)
- Bug 1213436 - Reject core dumps with node IDs that don't fit in an IEEE 754 double; r=sfink (3c1f6fdda0)
- Bug 1211006 - Add Debugger.Source.prototype.canonicalId; r=ejpbruel (eef7b79fce)
- Bug 1199218 - Implement JS::ubi::Node::size for js::LazyScript referents; r=sfink (098a48d240)
- Bug 1220031 - Add JS::ubi::Node::scriptFilename; r=sfink (6b824ae680)
- Bug 1143575. Remove unused MediaQueue::Empty. r=cpearce (de737f3433)
- Bug 1209933 - Make sure all parent runtime pointers are the topmost parent, r=billm. (fe824d967d)
- Bug 1197012 - Fix ThrowTypeError in Notification. r=mccr8 (0b1a097526)
- Bug 1197893 - Check the number of arguments for ThrowTypeError() and ThrowRangeError() at compile time. r=peterv (d98c7d78a0)
- Bug 1142083 - Add test for IDN Unicode domain redirect. r=mcmanus (0c8961fe17)
- Bug 1187159 - Add mochitest for loading packaged apps (iframe+fetch+mozapp) r=jduell (ce90ea561b)
- Bug 1186290 - Notify TabParent to switch process when loading a signed package. r=honzab, r=kanru. (c58a14554a)
- fix (15e2df75eb)
- Bug 1206124 P1 Fix "same-origin" CORS credentials in FetchDriver. r=ehsan (fae1bb6ab3)
- Bug 1206124 P2 Test fetch() with credentials and redirects. r=ehsan (ffc6254112)
- Bug 1211751: Remove nsIChannelEventSink-forwarding from EventSource and FetchDriver. It's never needed. r=smaug (adafe5737a)
- Bug 1212433 Fail fetch() calls that require preflight and also redirect. r=sicking a=abillings (c0d6742b9e)
- Bug 1193128 - Fix base64 decoding when fetching data URIs. r=baku (80bafa291a)
- Bug 1195167 part 1: Let necko handle all protocols. r=bkelly (bb932b0ada)
- Bug 1195167 part 2: Remove redundant aCORSFlag argument and instead use mCORSFlagEverSet. r=bkelly (beadafcad0)
- Bug 1195167 part 3: Remove more scheme-specific handling from FetchDriver. r=bkelly (d00b38db9e)
- Bug 1195167 part 4: Remove FetchDriver::BasicFetch since it is empty. r=bkelly (c5ed097267)
- Bug 1210413 P2 Test CORS credentials on cross-origin redirects. r=sicking a=dveditz (b4eeb8aac0)
- Bug 1210413 P1 Propagate new channel load flags from child to parent on redirect. r=jduell a=dveditz (8b329af4fa)
- Bug 1195167 part 5: Make FetchDriver use AsyncOpen2. r=bkelly (cc217c4cc1)
- Bug 1195167 part 6: Some code simplification since necko handles fetch recursion. r=bkelly (f3b6da2262)
- Bug 1195167: Followup to fix test which I forgot to change (81e7439a2e)
- Bug 1215746: Remove RequestMode::Cors_with_forced_preflight. r=bkelly (0336e812b6)
- Bug 1211000: Move CORS preflight logic from nsCORSListenerProxy to nsCORSPreflightListener. r=ehsan (bf2f71cf22)
- missing bit of Bug 1211443 - Drop scheduled update if decoder initialization isn't done yet. r=jya (f6bc074e33)
- Bug 1182571: Fix nsILoadInfo->GetContentPolicyType API to be less ambigious. Audit and fix all users of it. r=ckerschb (5af6fa7442)
- fix (e40c8e7625)
- Bug 1173811 - Part 1: Propagate the response URL to intercepted channels when necessary (non-e10s). r=mayhemer,bkelly (26f4f13c28)
- Bug 1173811 - Part 2: Propagate the response URL to intercepted channels when necessary (e10s). r=mayhemer,bkelly (a603fe1df2)
- Bug 1154309 - Add New Resource Timing Fields r=bz,hurley (1d14eb6bef)
- Bug 1175685 - add OriginAttribute to LoadInfo. r=jonas, r=ckerschb, r=michal (a5d18bb637)
- Bug 1175685 - add OriginAttribute to LoadInfo. r=jonas, r=ckerschb, r=michal (fb07d2c8aa)
- Bug 1212904 P1 Add a LoadTainting enumeration. r=jduell (a1db8a3e99)
- Bug 1212904 P2 Add LoadTainting information to nsILoadInfo. r=jduell (2482e5e334)
- Bug 1221151 - use [infallible] in nsILoadInfo.idl instead of manual %{C++ blocks; r=jduell (aae73129b6)
- Bug 1045891 - CSP 2 child-src implementation r=ckerschb (792920aeb9)
- Bug 1219931 - CSP: Don't allow removing a policy (r=sicking) (9daaab4186)
- Bug 1208661 - Dump client-side layer textures. r=BenWa (1f2d17d515)
2022-10-17 09:03:43 +08:00

1581 lines
46 KiB
C++

/* 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<ICameraControl> nsDOMCameraControl::sCachedCameraControl;
/* static */ nsresult nsDOMCameraControl::sCachedCameraControlStartResult = NS_OK;
/* static */ nsCOMPtr<nsITimer> 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<nsISupports*>(static_cast<void*>(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);
}
class mozilla::StartRecordingHelper : public nsIDOMEventListener
{
public:
NS_DECL_ISUPPORTS
NS_DECL_NSIDOMEVENTLISTENER
explicit StartRecordingHelper(nsDOMCameraControl* aDOMCameraControl)
: mDOMCameraControl(aDOMCameraControl)
{
MOZ_COUNT_CTOR(StartRecordingHelper);
}
protected:
virtual ~StartRecordingHelper()
{
MOZ_COUNT_DTOR(StartRecordingHelper);
}
protected:
RefPtr<nsDOMCameraControl> mDOMCameraControl;
};
NS_IMETHODIMP
StartRecordingHelper::HandleEvent(nsIDOMEvent* aEvent)
{
nsString eventType;
aEvent->GetType(eventType);
mDOMCameraControl->OnCreatedFileDescriptor(eventType.EqualsLiteral("success"));
return NS_OK;
}
NS_IMPL_ISUPPORTS(mozilla::StartRecordingHelper, 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.
RefPtr<ICameraControl> cameraControl = ICameraControl::Create(kDefaultCameraId);
if (NS_WARN_IF(!cameraControl)) {
return;
}
sCachedCameraControlStartResult = cameraControl->Start();
if (NS_WARN_IF(NS_FAILED(sCachedCameraControlStartResult))) {
return;
}
sCachedCameraControl = cameraControl;
nsCOMPtr<nsITimer> 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)
, mSetInitialConfig(false)
{
DOM_CAMERA_LOGT("%s:%d : this=%p\n", __func__, __LINE__, this);
mInput = new CameraPreviewMediaStream(this);
BindToOwner(aWindow);
RefPtr<DOMCameraConfiguration> 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<JSObject*> 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<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;
}
nsTArray<ICameraControl::Region> regionArray;
if (aValue.WasPassed()) {
const Sequence<CameraRegion>& 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 &region = 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<CameraRegion>& aValue)
{
nsTArray<ICameraControl::Region> 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<CameraRegion>& aAreas, ErrorResult& aRv)
{
aRv = Get(CAMERA_PARAM_METERINGAREAS, aAreas);
}
void
nsDOMCameraControl::SetMeteringAreas(const Optional<Sequence<CameraRegion> >& aMeteringAreas, ErrorResult& aRv)
{
aRv = Set(CAMERA_PARAM_METERINGAREAS, aMeteringAreas,
mCurrentConfiguration->mMaxMeteringAreas);
}
void
nsDOMCameraControl::GetFocusAreas(nsTArray<CameraRegion>& aAreas, ErrorResult& aRv)
{
aRv = Get(CAMERA_PARAM_FOCUSAREAS, aAreas);
}
void
nsDOMCameraControl::SetFocusAreas(const Optional<Sequence<CameraRegion> >& 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<dom::CameraCapabilities>
nsDOMCameraControl::Capabilities()
{
if (!mCameraControl) {
DOM_CAMERA_LOGW("mCameraControl is null at %s:%d\n", __func__, __LINE__);
return nullptr;
}
RefPtr<CameraCapabilities> caps = mCapabilities;
if (!caps) {
caps = new CameraCapabilities(mWindow, mCameraControl);
mCapabilities = caps;
}
return caps.forget();
}
// Methods.
already_AddRefed<Promise>
nsDOMCameraControl::StartRecording(const CameraStartRecordingOptions& aOptions,
nsDOMDeviceStorage& aStorageArea,
const nsAString& aFilename,
ErrorResult& aRv)
{
DOM_CAMERA_LOGT("%s:%d : this=%p\n", __func__, __LINE__, this);
RefPtr<Promise> promise = CreatePromise(aRv);
if (aRv.Failed()) {
return nullptr;
}
if (mStartRecordingPromise || mRecording) {
promise->MaybeReject(NS_ERROR_IN_PROGRESS);
return promise.forget();
}
aRv = NotifyRecordingStatusChange(NS_LITERAL_STRING("starting"));
if (aRv.Failed()) {
return nullptr;
}
mDSFileDescriptor = new DeviceStorageFileDescriptor();
RefPtr<DOMRequest> request = aStorageArea.CreateFileDescriptor(aFilename,
mDSFileDescriptor.get(),
aRv);
if (aRv.Failed()) {
NotifyRecordingStatusChange(NS_LITERAL_STRING("shutdown"));
return nullptr;
}
mStartRecordingPromise = promise;
mOptions = aOptions;
EventListenerManager* elm = request->GetOrCreateListenerManager();
if (!elm) {
NotifyRecordingStatusChange(NS_LITERAL_STRING("shutdown"));
aRv.Throw(NS_ERROR_UNEXPECTED);
return nullptr;
}
mRecording = true;
nsCOMPtr<nsIDOMEventListener> listener = new StartRecordingHelper(this);
elm->AddEventListener(NS_LITERAL_STRING("success"), listener, false, false);
elm->AddEventListener(NS_LITERAL_STRING("error"), listener, false, false);
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;
} 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;
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.
RefPtr<CloseFileRunnable> 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<Promise>
nsDOMCameraControl::SetConfiguration(const CameraConfiguration& aConfiguration,
ErrorResult& aRv)
{
DOM_CAMERA_LOGT("%s:%d : this=%p\n", __func__, __LINE__, this);
THROW_IF_NO_CAMERACONTROL(nullptr);
RefPtr<Promise> 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<Promise>
nsDOMCameraControl::AutoFocus(ErrorResult& aRv)
{
DOM_CAMERA_LOGT("%s:%d : this=%p\n", __func__, __LINE__, this);
THROW_IF_NO_CAMERACONTROL(nullptr);
RefPtr<Promise> 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<Promise>
nsDOMCameraControl::TakePicture(const CameraPictureOptions& aOptions,
ErrorResult& aRv)
{
DOM_CAMERA_LOGT("%s:%d : this=%p\n", __func__, __LINE__, this);
THROW_IF_NO_CAMERACONTROL(nullptr);
RefPtr<Promise> 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<Promise>
nsDOMCameraControl::ReleaseHardware(ErrorResult& aRv)
{
DOM_CAMERA_LOGI("%s:%d : this=%p\n", __func__, __LINE__, this);
RefPtr<Promise> 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<Promise>
nsDOMCameraControl::CreatePromise(ErrorResult& aRv)
{
nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(mWindow);
if (!global) {
aRv.Throw(NS_ERROR_FAILURE);
return nullptr;
}
return Promise::Create(global, aRv);
}
void
nsDOMCameraControl::AbortPromise(RefPtr<Promise>& aPromise)
{
RefPtr<Promise> 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;
RefPtr<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.
RefPtr<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.
OnGetCameraComplete();
}
break;
case CameraControlListener::kHardwareClosed:
DOM_CAMERA_LOGI("DOM OnHardwareStateChange: closed\n");
if (!mSetInitialConfig) {
RefPtr<Promise> 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;
}
RefPtr<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()
{
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::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:
{
RefPtr<Promise> promise = mStartRecordingPromise.forget();
if (promise) {
promise->MaybeResolve(JS::UndefinedHandleValue);
}
state = NS_LITERAL_STRING("Started");
}
break;
case CameraControlListener::kRecorderStopped:
NotifyRecordingStatusChange(NS_LITERAL_STRING("shutdown"));
state = NS_LITERAL_STRING("Stopped");
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);
}
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;
}
RefPtr<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);
RefPtr<CameraConfigurationEvent> 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());
RefPtr<Promise> 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<ICameraControl::Face>& aFaces)
{
DOM_CAMERA_LOGI("DOM OnFacesDetected %zu face(s)\n", aFaces.Length());
MOZ_ASSERT(NS_IsMainThread());
Sequence<OwningNonNull<DOMCameraDetectedFace> > 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<DOMMediaStream*>(this), aFaces[i]);
}
}
CameraFacesDetectedEventInit eventInit;
eventInit.mFaces.SetValue(faces);
RefPtr<CameraFacesDetectedEvent> 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);
RefPtr<Promise> promise = mTakePicturePromise.forget();
if (promise) {
nsCOMPtr<nsIDOMBlob> picture = aPicture;
promise->MaybeResolve(picture);
}
RefPtr<Blob> blob = static_cast<Blob*>(aPicture);
BlobEventInit eventInit;
eventInit.mData = blob;
RefPtr<BlobEvent> 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());
RefPtr<Promise> 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) {
// 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);
}