From fea4fa6a847764eb60d9b1ae334dc9520c96f79f Mon Sep 17 00:00:00 2001 From: Matthew Gregan Date: Mon, 16 Jul 2012 17:21:04 -0400 Subject: [PATCH 01/18] Bug 768333 - nsBufferedAudioStream::GetPositionInFrames miscompiled with Win32 PGO. r=cpearce --- content/html/content/src/nsHTMLAudioElement.cpp | 8 ++++++-- content/media/nsAudioStream.cpp | 11 +++++++++-- 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/content/html/content/src/nsHTMLAudioElement.cpp b/content/html/content/src/nsHTMLAudioElement.cpp index bc51c082367..c2b223a9208 100644 --- a/content/html/content/src/nsHTMLAudioElement.cpp +++ b/content/html/content/src/nsHTMLAudioElement.cpp @@ -185,11 +185,15 @@ nsHTMLAudioElement::MozCurrentSampleOffset(PRUint64 *aRetVal) return NS_ERROR_DOM_INVALID_STATE_ERR; } - *aRetVal = mAudioStream->GetPositionInFrames() * mChannels; + PRInt64 position = mAudioStream->GetPositionInFrames(); + if (position < 0) { + *aRetVal = 0; + } else { + *aRetVal = mAudioStream->GetPositionInFrames() * mChannels; + } return NS_OK; } - nsresult nsHTMLAudioElement::SetAcceptHeader(nsIHttpChannel* aChannel) { nsCAutoString value( diff --git a/content/media/nsAudioStream.cpp b/content/media/nsAudioStream.cpp index e4020322968..282d4494541 100644 --- a/content/media/nsAudioStream.cpp +++ b/content/media/nsAudioStream.cpp @@ -1147,12 +1147,19 @@ nsBufferedAudioStream::GetPosition() return -1; } +// This function is miscompiled by PGO with MSVC 2010. See bug 768333. +#ifdef _MSC_VER +#pragma optimize("", off) +#endif PRInt64 nsBufferedAudioStream::GetPositionInFrames() { MonitorAutoLock mon(mMonitor); return GetPositionInFramesUnlocked(); } +#ifdef _MSC_VER +#pragma optimize("", on) +#endif PRInt64 nsBufferedAudioStream::GetPositionInFramesUnlocked() @@ -1173,11 +1180,11 @@ nsBufferedAudioStream::GetPositionInFramesUnlocked() // Adjust the reported position by the number of silent frames written // during stream underruns. - PRInt64 adjustedPosition = 0; + PRUint64 adjustedPosition = 0; if (position >= mLostFrames) { adjustedPosition = position - mLostFrames; } - return adjustedPosition; + return NS_MIN(adjustedPosition, PR_INT64_MAX); } bool From 47efc774e37e24978e88a12590819519fdf1ad11 Mon Sep 17 00:00:00 2001 From: Matthew Gregan Date: Mon, 16 Jul 2012 17:15:24 -0400 Subject: [PATCH 02/18] Bug 761274 - Work around buffer sizing bug in PulseAudio ALSA plugin. r=doublec --- content/media/nsAudioStream.cpp | 9 ++++---- media/libcubeb/AUTHORS | 1 + media/libcubeb/README_MOZILLA | 2 +- media/libcubeb/include/cubeb.h | 22 ++++++++++--------- media/libcubeb/src/cubeb_alsa.c | 32 ++++++++++++++++++++++++++++ media/libcubeb/src/cubeb_audiounit.c | 6 ++++++ media/libcubeb/src/cubeb_pulse.c | 12 ++++++++--- media/libcubeb/src/cubeb_winmm.c | 9 +++++++- 8 files changed, 73 insertions(+), 20 deletions(-) diff --git a/content/media/nsAudioStream.cpp b/content/media/nsAudioStream.cpp index 282d4494541..31015dfc9ea 100644 --- a/content/media/nsAudioStream.cpp +++ b/content/media/nsAudioStream.cpp @@ -880,13 +880,13 @@ private: return static_cast(aThis)->DataCallback(aBuffer, aFrames); } - static int StateCallback_S(cubeb_stream*, void* aThis, cubeb_state aState) + static void StateCallback_S(cubeb_stream*, void* aThis, cubeb_state aState) { - return static_cast(aThis)->StateCallback(aState); + static_cast(aThis)->StateCallback(aState); } long DataCallback(void* aBuffer, long aFrames); - int StateCallback(cubeb_state aState); + void StateCallback(cubeb_state aState); // Shared implementation of underflow adjusted position calculation. // Caller must own the monitor. @@ -1268,7 +1268,7 @@ nsBufferedAudioStream::DataCallback(void* aBuffer, long aFrames) return aFrames - (bytesWanted / mBytesPerFrame); } -int +void nsBufferedAudioStream::StateCallback(cubeb_state aState) { MonitorAutoLock mon(mMonitor); @@ -1278,7 +1278,6 @@ nsBufferedAudioStream::StateCallback(cubeb_state aState) mState = ERRORED; } mon.NotifyAll(); - return CUBEB_OK; } #endif diff --git a/media/libcubeb/AUTHORS b/media/libcubeb/AUTHORS index 8204f40f474..b7d5fc1484a 100644 --- a/media/libcubeb/AUTHORS +++ b/media/libcubeb/AUTHORS @@ -1 +1,2 @@ Matthew Gregan +Alexandre Ratchov diff --git a/media/libcubeb/README_MOZILLA b/media/libcubeb/README_MOZILLA index cfb688d5474..38e4f7c15f3 100644 --- a/media/libcubeb/README_MOZILLA +++ b/media/libcubeb/README_MOZILLA @@ -5,4 +5,4 @@ Makefile.in build files for the Mozilla build system. The cubeb git repository is: git://github.com/kinetiknz/cubeb.git -The git commit ID used was 21d9678eb9755761f7a9d26253189b926258de7c. +The git commit ID used was f3116936eba69aced240df7db77264269f3f95ae. diff --git a/media/libcubeb/include/cubeb.h b/media/libcubeb/include/cubeb.h index fc9c10257a7..32e0b4d6c2f 100644 --- a/media/libcubeb/include/cubeb.h +++ b/media/libcubeb/include/cubeb.h @@ -40,8 +40,8 @@ extern "C" { cubeb_stream_start(stm); for (;;) { - cubeb_get_time(stm, &ts); - printf("time=%lu\n", ts); + cubeb_stream_get_position(stm, &ts); + printf("time=%llu\n", ts); sleep(1); } cubeb_stream_stop(stm); @@ -64,10 +64,9 @@ extern "C" { @endcode @code - int state_cb(cubeb_stream * stm, void * user, cubeb_state state) + void state_cb(cubeb_stream * stm, void * user, cubeb_state state) { printf("state=%d\n", state); - return CUBEB_OK; } @endcode */ @@ -141,12 +140,10 @@ typedef long (* cubeb_data_callback)(cubeb_stream * stream, /** User supplied state callback. @param stream @param user_ptr - @param state - @retval CUBEB_OK - @retval CUBEB_ERROR */ -typedef int (* cubeb_state_callback)(cubeb_stream * stream, - void * user_ptr, - cubeb_state state); + @param state */ +typedef void (* cubeb_state_callback)(cubeb_stream * stream, + void * user_ptr, + cubeb_state state); /** Initialize an application context. This will perform any library or application scoped initialization. @@ -156,6 +153,11 @@ typedef int (* cubeb_state_callback)(cubeb_stream * stream, @retval CUBEB_ERROR */ int cubeb_init(cubeb ** context, char const * context_name); +/** Get a read-only string identifying this context's current backend. + @param context + @retval Read-only string identifying current backend. */ +char const * cubeb_get_backend_id(cubeb * context); + /** Destroy an application context. @param context */ void cubeb_destroy(cubeb * context); diff --git a/media/libcubeb/src/cubeb_alsa.c b/media/libcubeb/src/cubeb_alsa.c index 599aea26ff8..d7235b679ad 100644 --- a/media/libcubeb/src/cubeb_alsa.c +++ b/media/libcubeb/src/cubeb_alsa.c @@ -20,6 +20,8 @@ #define CUBEB_WATCHDOG_MS 10000 #define UNUSED __attribute__ ((__unused__)) +#define ALSA_PA_PLUGIN "ALSA <-> PulseAudio PCM I/O Plugin" + /* ALSA is not thread-safe. snd_pcm_t instances are individually protected by the owning cubeb_stream's mutex. snd_pcm_t creation and destruction is not thread-safe until ALSA 1.0.24 (see alsa-lib.git commit 91c9c8f1), @@ -474,6 +476,24 @@ silent_error_handler(char const * file UNUSED, int line UNUSED, char const * fun { } +static int +pcm_uses_pulseaudio_plugin(snd_pcm_t * pcm) +{ + snd_output_t * out; + char * buf; + size_t bufsz; + int r; + + snd_output_buffer_open(&out); + snd_pcm_dump(pcm, out); + bufsz = snd_output_buffer_string(out, &buf); + r = bufsz >= strlen(ALSA_PA_PLUGIN) && + strncmp(buf, ALSA_PA_PLUGIN, strlen(ALSA_PA_PLUGIN)) == 0; + snd_output_close(out); + + return r; +} + int cubeb_init(cubeb ** context, char const * context_name UNUSED) { @@ -531,6 +551,12 @@ cubeb_init(cubeb ** context, char const * context_name UNUSED) return CUBEB_OK; } +char const * +cubeb_get_backend_id(cubeb * ctx UNUSED) +{ + return "alsa"; +} + void cubeb_destroy(cubeb * ctx) { @@ -621,6 +647,12 @@ cubeb_stream_init(cubeb * ctx, cubeb_stream ** stream, char const * stream_name r = snd_pcm_nonblock(stm->pcm, 1); assert(r == 0); + /* Ugly hack: the PA ALSA plugin allows buffer configurations that can't + possibly work. See https://bugzilla.mozilla.org/show_bug.cgi?id=761274 */ + if (pcm_uses_pulseaudio_plugin(stm->pcm)) { + latency = latency < 200 ? 200 : latency; + } + r = snd_pcm_set_params(stm->pcm, format, SND_PCM_ACCESS_RW_INTERLEAVED, stm->params.channels, stm->params.rate, 1, latency * 1000); diff --git a/media/libcubeb/src/cubeb_audiounit.c b/media/libcubeb/src/cubeb_audiounit.c index dde19425084..fff013074f9 100644 --- a/media/libcubeb/src/cubeb_audiounit.c +++ b/media/libcubeb/src/cubeb_audiounit.c @@ -87,6 +87,12 @@ cubeb_init(cubeb ** context, char const * context_name) return CUBEB_OK; } +char const * +cubeb_get_backend_id(cubeb * ctx) +{ + return "audiounit"; +} + void cubeb_destroy(cubeb * ctx) { diff --git a/media/libcubeb/src/cubeb_pulse.c b/media/libcubeb/src/cubeb_pulse.c index de861719b30..9de43be0daf 100644 --- a/media/libcubeb/src/cubeb_pulse.c +++ b/media/libcubeb/src/cubeb_pulse.c @@ -116,9 +116,9 @@ stream_request_callback(pa_stream * s, size_t nbytes, void * u) if ((size_t) got < size / frame_size) { size_t buffer_fill = pa_stream_get_buffer_attr(s)->maxlength - pa_stream_writable_size(s); - double buffer_time = (double) buffer_fill / stm->sample_spec.rate; + pa_usec_t buffer_time = pa_bytes_to_usec(buffer_fill, &stm->sample_spec); /* pa_stream_drain is useless, see PA bug# 866. this is a workaround. */ - stm->drain_timer = pa_context_rttime_new(stm->context->context, pa_rtclock_now() + buffer_time * 1e6, stream_drain_callback, stm); + stm->drain_timer = pa_context_rttime_new(stm->context->context, pa_rtclock_now() + buffer_time, stream_drain_callback, stm); stm->shutdown = 1; return; } @@ -211,6 +211,12 @@ cubeb_init(cubeb ** context, char const * context_name) return CUBEB_OK; } +char const * +cubeb_get_backend_id(cubeb * ctx) +{ + return "pulse"; +} + void cubeb_destroy(cubeb * ctx) { @@ -295,7 +301,7 @@ cubeb_stream_init(cubeb * context, cubeb_stream ** stream, char const * stream_n battr.maxlength = -1; battr.tlength = pa_usec_to_bytes(latency * PA_USEC_PER_MSEC, &stm->sample_spec); battr.prebuf = -1; - battr.minreq = battr.tlength / 2; + battr.minreq = battr.tlength / 4; battr.fragsize = -1; pa_threaded_mainloop_lock(stm->context->mainloop); diff --git a/media/libcubeb/src/cubeb_winmm.c b/media/libcubeb/src/cubeb_winmm.c index 75d5542472b..01e0fb0a54b 100644 --- a/media/libcubeb/src/cubeb_winmm.c +++ b/media/libcubeb/src/cubeb_winmm.c @@ -1,5 +1,5 @@ /* - * Copyright  2011 Mozilla Foundation + * Copyright © 2011 Mozilla Foundation * * This program is made available under an ISC-style license. See the * accompanying file LICENSE for details. @@ -123,6 +123,7 @@ cubeb_refill_stream(cubeb_stream * stm) got = stm->data_callback(stm, stm->user_ptr, hdr->lpData, wanted); EnterCriticalSection(&stm->lock); if (got < 0) { + LeaveCriticalSection(&stm->lock); /* XXX handle this case */ assert(0); return; @@ -226,6 +227,12 @@ cubeb_init(cubeb ** context, char const * context_name) return CUBEB_OK; } +char const * +cubeb_get_backend_id(cubeb * ctx) +{ + return "winmm"; +} + void cubeb_destroy(cubeb * ctx) { From bf3937cc1124bce808f87f1ee6378f772a95b51d Mon Sep 17 00:00:00 2001 From: Nicholas Cameron Date: Thu, 26 Jul 2012 14:30:16 +1200 Subject: [PATCH 03/18] bug 773460; turn on Azure/Cairo canvas for Windows. r=roc --HG-- extra : rebase_source : 10174a9e297a4760336b0e6332b27a249191ec96 --- modules/libpref/src/init/all.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/modules/libpref/src/init/all.js b/modules/libpref/src/init/all.js index 3f349ced999..b79c5f39343 100644 --- a/modules/libpref/src/init/all.js +++ b/modules/libpref/src/init/all.js @@ -226,10 +226,10 @@ pref("gfx.font_rendering.directwrite.use_gdi_table_loading", true); #endif #ifdef XP_WIN -pref("gfx.canvas.azure.enabled", true); // comma separated list of backends to use in order of preference // e.g., pref("gfx.canvas.azure.backends", "direct2d,skia,cairo"); -pref("gfx.canvas.azure.backends", "direct2d"); +pref("gfx.canvas.azure.enabled", true); +pref("gfx.canvas.azure.backends", "direct2d,cairo"); pref("gfx.content.azure.enabled", true); #else #ifdef XP_MACOSX @@ -237,7 +237,7 @@ pref("gfx.canvas.azure.enabled", true); pref("gfx.canvas.azure.backends", "cg"); #else pref("gfx.canvas.azure.enabled", false); -pref("gfx.canvas.azure.backends", "cairo,skia"); +pref("gfx.canvas.azure.backends", "cairo"); #endif #endif From 60afe29f932f0fb946211cfed53da2605e4e93fa Mon Sep 17 00:00:00 2001 From: Benjamin Peterson Date: Mon, 30 Jul 2012 20:59:25 -0700 Subject: [PATCH 04/18] Backout 56e0971c81ea for oth test bustage --- browser/base/content/browser-social.js | 4 ++-- toolkit/components/social/SocialService.jsm | 14 -------------- 2 files changed, 2 insertions(+), 16 deletions(-) diff --git a/browser/base/content/browser-social.js b/browser/base/content/browser-social.js index 078e607c8dc..e48e8724bba 100644 --- a/browser/base/content/browser-social.js +++ b/browser/base/content/browser-social.js @@ -160,10 +160,10 @@ let SocialShareButton = { updateProfileInfo: function SSB_updateProfileInfo() { let profileRow = document.getElementById("editSharePopupHeader"); let profile = Social.provider.profile; - if (profile && profile.displayName) { + if (profile && profile.portrait && profile.displayName) { profileRow.hidden = false; let portrait = document.getElementById("socialUserPortrait"); - portrait.setAttribute("src", profile.portrait || "chrome://browser/skin/social/social.png"); + portrait.style.listStyleImage = profile.portrait; let displayName = document.getElementById("socialUserDisplayName"); displayName.setAttribute("label", profile.displayName); } else { diff --git a/toolkit/components/social/SocialService.jsm b/toolkit/components/social/SocialService.jsm index 3ba910f78a9..7bafd1881a7 100644 --- a/toolkit/components/social/SocialService.jsm +++ b/toolkit/components/social/SocialService.jsm @@ -201,20 +201,6 @@ SocialProvider.prototype = { updateUserProfile: function(profile) { this.profile = profile; - // Sanitize the portrait from any potential script-injection. - if (profile.portrait) { - try { - let portraitUri = Services.io.newURI(profile.portrait, null, null); - - let scheme = portraitUri ? portraitUri.scheme : ""; - if (scheme != "data" && scheme != "http" && scheme != "https") { - profile.portrait = ""; - } - } catch (ex) { - profile.portrait = ""; - } - } - if (profile.iconURL) this.iconURL = profile.iconURL; From 0f7ac14a7b67a48087dfb6362a460df118497020 Mon Sep 17 00:00:00 2001 From: Andrew McCreight Date: Mon, 30 Jul 2012 21:01:59 -0700 Subject: [PATCH 05/18] Bug 761422, part 1 - Clone before we adjust XPC maps in ReparentWrapperIfFound. r=bholley --- js/xpconnect/src/XPCWrappedNative.cpp | 46 ++++++++++++++++----------- 1 file changed, 27 insertions(+), 19 deletions(-) diff --git a/js/xpconnect/src/XPCWrappedNative.cpp b/js/xpconnect/src/XPCWrappedNative.cpp index 17cbbf4b391..7c93472238f 100644 --- a/js/xpconnect/src/XPCWrappedNative.cpp +++ b/js/xpconnect/src/XPCWrappedNative.cpp @@ -1540,10 +1540,35 @@ XPCWrappedNative::ReparentWrapperIfFound(XPCCallContext& ccx, } if (wrapper) { - Native2WrappedNativeMap* oldMap = aOldScope->GetWrappedNativeMap(); - Native2WrappedNativeMap* newMap = aNewScope->GetWrappedNativeMap(); + + // First, the clone of the reflector, get a copy of its + // properties and clone its expando chain. The only part that is + // dangerous here if we have to return early is that we must avoid + // ending up with two reflectors pointing to the same WN. Other than + // that, the objects we create will just go away if we return early. + + JSObject *newobj = JS_CloneObject(ccx, flat, + newProto->GetJSProtoObject(), + aNewParent); + if (!newobj) + return NS_ERROR_FAILURE; + + JSObject *propertyHolder = + JS_NewObjectWithGivenProto(ccx, NULL, NULL, aNewParent); + if (!propertyHolder) + return NS_ERROR_OUT_OF_MEMORY; + if (!JS_CopyPropertiesFrom(ccx, propertyHolder, flat)) + return NS_ERROR_FAILURE; + + // Expandos from other compartments are attached to the target JS object. + // Copy them over, and let the old ones die a natural death. + SetExpandoChain(newobj, nullptr); + if (!XrayUtils::CloneExpandoChain(ccx, newobj, flat)) + return NS_ERROR_FAILURE; { // scoped lock + Native2WrappedNativeMap* oldMap = aOldScope->GetWrappedNativeMap(); + Native2WrappedNativeMap* newMap = aNewScope->GetWrappedNativeMap(); XPCAutoLock lock(aOldScope->GetRuntime()->GetMapLock()); oldMap->Remove(wrapper); @@ -1576,23 +1601,6 @@ XPCWrappedNative::ReparentWrapperIfFound(XPCCallContext& ccx, (void) newMap->Add(wrapper); } - JSObject *newobj = JS_CloneObject(ccx, flat, - newProto->GetJSProtoObject(), - aNewParent); - if (!newobj) - return NS_ERROR_FAILURE; - - JSObject *propertyHolder = - JS_NewObjectWithGivenProto(ccx, NULL, NULL, aNewParent); - if (!propertyHolder || !JS_CopyPropertiesFrom(ccx, propertyHolder, flat)) - return NS_ERROR_OUT_OF_MEMORY; - - // Expandos from other compartments are attached to the target JS object. - // Copy them over, and let the old ones die a natural death. - SetExpandoChain(newobj, nullptr); - if (!XrayUtils::CloneExpandoChain(ccx, newobj, flat)) - return NS_ERROR_FAILURE; - // Before proceeding, eagerly create any same-compartment security wrappers // that the object might have. This forces us to take the 'WithWrapper' path // while transplanting that handles this stuff correctly. From 40af3f379de6c57d4125f078adcc5c11ce56be32 Mon Sep 17 00:00:00 2001 From: Andrew McCreight Date: Mon, 30 Jul 2012 21:01:59 -0700 Subject: [PATCH 06/18] Bug 761422, part 2 - guard against double reflectors on failure in ReparentWrapperIfFound. r=bholley --- js/xpconnect/src/XPCWrappedNative.cpp | 73 +++++++++++++++++++-------- 1 file changed, 51 insertions(+), 22 deletions(-) diff --git a/js/xpconnect/src/XPCWrappedNative.cpp b/js/xpconnect/src/XPCWrappedNative.cpp index 7c93472238f..4cba2c4a925 100644 --- a/js/xpconnect/src/XPCWrappedNative.cpp +++ b/js/xpconnect/src/XPCWrappedNative.cpp @@ -1450,6 +1450,27 @@ private: JSObject* mPreservedWrapper; }; +// Dynamically ensure that two objects don't end up with the same private. +class AutoClonePrivateGuard NS_STACK_CLASS { +public: + AutoClonePrivateGuard(JSObject *aOld, JSObject *aNew) + : mOldReflector(aOld), mNewReflector(aNew) + { + MOZ_ASSERT(JS_GetPrivate(aOld) == JS_GetPrivate(aNew)); + } + + ~AutoClonePrivateGuard() + { + if (JS_GetPrivate(mOldReflector)) { + JS_SetPrivate(mNewReflector, nullptr); + } + } + +private: + JSObject* mOldReflector; + JSObject* mNewReflector; +}; + // static nsresult XPCWrappedNative::ReparentWrapperIfFound(XPCCallContext& ccx, @@ -1553,18 +1574,37 @@ XPCWrappedNative::ReparentWrapperIfFound(XPCCallContext& ccx, if (!newobj) return NS_ERROR_FAILURE; - JSObject *propertyHolder = - JS_NewObjectWithGivenProto(ccx, NULL, NULL, aNewParent); - if (!propertyHolder) - return NS_ERROR_OUT_OF_MEMORY; - if (!JS_CopyPropertiesFrom(ccx, propertyHolder, flat)) - return NS_ERROR_FAILURE; + // At this point, both |flat| and |newobj| point to the same wrapped + // native, which is bad, because one of them will end up finalizing + // a wrapped native it does not own. |cloneGuard| ensures that if we + // exit before calling clearing |flat|'s private the private of + // |newobj| will be set to NULL. |flat| will go away soon, because + // we swap it with another object during the transplant and let that + // object die. + JSObject *propertyHolder; + { + AutoClonePrivateGuard cloneGuard(flat, newobj); - // Expandos from other compartments are attached to the target JS object. - // Copy them over, and let the old ones die a natural death. - SetExpandoChain(newobj, nullptr); - if (!XrayUtils::CloneExpandoChain(ccx, newobj, flat)) - return NS_ERROR_FAILURE; + propertyHolder = JS_NewObjectWithGivenProto(ccx, NULL, NULL, aNewParent); + if (!propertyHolder) + return NS_ERROR_OUT_OF_MEMORY; + if (!JS_CopyPropertiesFrom(ccx, propertyHolder, flat)) + return NS_ERROR_FAILURE; + + // Expandos from other compartments are attached to the target JS object. + // Copy them over, and let the old ones die a natural death. + SetExpandoChain(newobj, nullptr); + if (!XrayUtils::CloneExpandoChain(ccx, newobj, flat)) + return NS_ERROR_FAILURE; + + // We've set up |newobj|, so we make it own the WN by nulling out + // the private of |flat|. + // + // NB: It's important to do this _after_ copying the properties to + // propertyHolder. Otherwise, an object with |foo.x === foo| will + // crash when JS_CopyPropertiesFrom tries to call wrap() on foo.x. + JS_SetPrivate(flat, nullptr); + } { // scoped lock Native2WrappedNativeMap* oldMap = aOldScope->GetWrappedNativeMap(); @@ -1611,17 +1651,6 @@ XPCWrappedNative::ReparentWrapperIfFound(XPCCallContext& ccx, return NS_ERROR_FAILURE; } - // Null out the private of the JS reflector. If we don't, we'll end up - // with two JS objects with the same WN in their private slot, and both - // will try to delete it during finalization. The one in this - // compartment will actually go away quite soon, because we swap() it - // with another object during the transplant and let that object die. - // - // NB: It's important to do this _after_ copying the properties to - // propertyHolder. Otherwise, an object with |foo.x === foo| will - // crash when JS_CopyPropertiesFrom tries to call wrap() on foo.x. - JS_SetPrivate(flat, nullptr); - JSObject *ww = wrapper->GetWrapper(); if (ww) { JSObject *newwrapper; From e9759cb6ada90e9e06f60f93c3ebc548ddc26187 Mon Sep 17 00:00:00 2001 From: Andrew McCreight Date: Mon, 30 Jul 2012 21:01:59 -0700 Subject: [PATCH 07/18] Bug 761422, part 3 - get security wrappers before changing maps. r=bholley --- js/xpconnect/src/XPCWrappedNative.cpp | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/js/xpconnect/src/XPCWrappedNative.cpp b/js/xpconnect/src/XPCWrappedNative.cpp index 4cba2c4a925..8612458b6a0 100644 --- a/js/xpconnect/src/XPCWrappedNative.cpp +++ b/js/xpconnect/src/XPCWrappedNative.cpp @@ -1606,6 +1606,16 @@ XPCWrappedNative::ReparentWrapperIfFound(XPCCallContext& ccx, JS_SetPrivate(flat, nullptr); } + // Before proceeding, eagerly create any same-compartment security wrappers + // that the object might have. This forces us to take the 'WithWrapper' path + // while transplanting that handles this stuff correctly. + { + JSAutoEnterCompartment innerAC; + if (!innerAC.enter(ccx, aOldScope->GetGlobalJSObject()) || + !wrapper->GetSameCompartmentSecurityWrapper(ccx)) + return NS_ERROR_FAILURE; + } + { // scoped lock Native2WrappedNativeMap* oldMap = aOldScope->GetWrappedNativeMap(); Native2WrappedNativeMap* newMap = aNewScope->GetWrappedNativeMap(); @@ -1641,16 +1651,6 @@ XPCWrappedNative::ReparentWrapperIfFound(XPCCallContext& ccx, (void) newMap->Add(wrapper); } - // Before proceeding, eagerly create any same-compartment security wrappers - // that the object might have. This forces us to take the 'WithWrapper' path - // while transplanting that handles this stuff correctly. - { - JSAutoEnterCompartment innerAC; - if (!innerAC.enter(ccx, aOldScope->GetGlobalJSObject()) || - !wrapper->GetSameCompartmentSecurityWrapper(ccx)) - return NS_ERROR_FAILURE; - } - JSObject *ww = wrapper->GetWrapper(); if (ww) { JSObject *newwrapper; From 8df7caa03efd8cd3528f668068b5b7fa8fe0c5e2 Mon Sep 17 00:00:00 2001 From: Andrew McCreight Date: Mon, 30 Jul 2012 21:02:00 -0700 Subject: [PATCH 08/18] Bug 761422, part 4 - Don't try to be a hero in ReparentWrapperIfFound. r=bholley --- js/xpconnect/src/XPCWrappedNative.cpp | 27 +++++++++++++-------------- 1 file changed, 13 insertions(+), 14 deletions(-) diff --git a/js/xpconnect/src/XPCWrappedNative.cpp b/js/xpconnect/src/XPCWrappedNative.cpp index 8612458b6a0..5025db867c6 100644 --- a/js/xpconnect/src/XPCWrappedNative.cpp +++ b/js/xpconnect/src/XPCWrappedNative.cpp @@ -1616,6 +1616,8 @@ XPCWrappedNative::ReparentWrapperIfFound(XPCCallContext& ccx, return NS_ERROR_FAILURE; } + // Update scope maps. This section modifies global state, so from + // here on out we crash if anything fails. { // scoped lock Native2WrappedNativeMap* oldMap = aOldScope->GetWrappedNativeMap(); Native2WrappedNativeMap* newMap = aNewScope->GetWrappedNativeMap(); @@ -1648,7 +1650,8 @@ XPCWrappedNative::ReparentWrapperIfFound(XPCCallContext& ccx, NS_ASSERTION(!newMap->Find(wrapper->GetIdentityObject()), "wrapper already in new scope!"); - (void) newMap->Add(wrapper); + if (!newMap->Add(wrapper)) + MOZ_CRASH(); } JSObject *ww = wrapper->GetWrapper(); @@ -1659,41 +1662,37 @@ XPCWrappedNative::ReparentWrapperIfFound(XPCCallContext& ccx, if (xpc::WrapperFactory::IsLocationObject(flat)) { newwrapper = xpc::WrapperFactory::WrapLocationObject(ccx, newobj); if (!newwrapper) - return NS_ERROR_FAILURE; + MOZ_CRASH(); } else { NS_ASSERTION(wrapper->NeedsSOW(), "weird wrapper wrapper"); newwrapper = xpc::WrapperFactory::WrapSOWObject(ccx, newobj); if (!newwrapper) - return NS_ERROR_FAILURE; + MOZ_CRASH(); } // Ok, now we do the special object-plus-wrapper transplant. ww = xpc::TransplantObjectWithWrapper(ccx, flat, ww, newobj, newwrapper); if (!ww) - return NS_ERROR_FAILURE; + MOZ_CRASH(); flat = newobj; wrapper->SetWrapper(ww); } else { flat = xpc::TransplantObject(ccx, flat, newobj); if (!flat) - return NS_ERROR_FAILURE; + MOZ_CRASH(); } wrapper->mFlatJSObject = flat; if (cache) cache->SetWrapper(flat); if (!JS_CopyPropertiesFrom(ccx, flat, propertyHolder)) - return NS_ERROR_FAILURE; + MOZ_CRASH(); } else { SetSlimWrapperProto(flat, newProto.get()); - if (!JS_SetPrototype(ccx, flat, newProto->GetJSProtoObject())) { - // this is bad, very bad - SetSlimWrapperProto(flat, nullptr); - NS_ERROR("JS_SetPrototype failed"); - return NS_ERROR_FAILURE; - } + if (!JS_SetPrototype(ccx, flat, newProto->GetJSProtoObject())) + MOZ_CRASH(); // this is bad, very bad } // Call the scriptable hook to indicate that we transplanted. @@ -1706,13 +1705,13 @@ XPCWrappedNative::ReparentWrapperIfFound(XPCCallContext& ccx, if (aNewParent) { if (!JS_SetParent(ccx, flat, aNewParent)) - return NS_ERROR_FAILURE; + MOZ_CRASH(); JSObject *nw; if (wrapper && (nw = wrapper->GetWrapper()) && !JS_SetParent(ccx, nw, JS_GetGlobalForObject(ccx, aNewParent))) { - return NS_ERROR_FAILURE; + MOZ_CRASH(); } } From 711531853fa9acb18775cf2686b77c4459269221 Mon Sep 17 00:00:00 2001 From: Brian Nicholson Date: Mon, 30 Jul 2012 21:19:04 -0700 Subject: [PATCH 09/18] Bug 778561 - Remove YouTube UA hack. r=mfinkle --- mobile/android/chrome/content/browser.js | 7 ------- 1 file changed, 7 deletions(-) diff --git a/mobile/android/chrome/content/browser.js b/mobile/android/chrome/content/browser.js index 232fff22c0d..8761aeb1740 100644 --- a/mobile/android/chrome/content/browser.js +++ b/mobile/android/chrome/content/browser.js @@ -1960,13 +1960,6 @@ var UserAgent = { if (tab == null) break; - if (channel.URI.host.indexOf("youtube") != -1) { - let ua = Cc["@mozilla.org/network/protocol;1?name=http"].getService(Ci.nsIHttpProtocolHandler).userAgent; -#expand let version = "__MOZ_APP_VERSION__"; - ua += " Fennec/" + version; - channel.setRequestHeader("User-Agent", ua, false); - } - // Send desktop UA if "Request Desktop Site" is enabled if (tab.desktopMode) channel.setRequestHeader("User-Agent", this.DESKTOP_UA, false); From 32ecc36c05c2d94dee243a077c09f50cfe5fb3e3 Mon Sep 17 00:00:00 2001 From: Boris Zbarsky Date: Tue, 31 Jul 2012 00:22:22 -0400 Subject: [PATCH 10/18] Bug 771636 part 1. Rearrange default-value handling so we actually set C++ values directly instead of round-tripping through jsval. r=peterv --- dom/bindings/Codegen.py | 212 +++++++++++++++++++------- dom/bindings/parser/WebIDL.py | 3 +- dom/bindings/test/TestBindingHeader.h | 1 + dom/bindings/test/TestCodeGen.webidl | 15 +- 4 files changed, 170 insertions(+), 61 deletions(-) diff --git a/dom/bindings/Codegen.py b/dom/bindings/Codegen.py index 28627baa5ea..32ae9452896 100644 --- a/dom/bindings/Codegen.py +++ b/dom/bindings/Codegen.py @@ -1355,6 +1355,14 @@ builtinNames = { IDLType.Tags.double: 'double' } +numericTags = [ + IDLType.Tags.int8, IDLType.Tags.uint8, + IDLType.Tags.int16, IDLType.Tags.uint16, + IDLType.Tags.int32, IDLType.Tags.uint32, + IDLType.Tags.int64, IDLType.Tags.uint64, + IDLType.Tags.float, IDLType.Tags.double + ] + class CastableObjectUnwrapper(): """ A class for unwrapping an object named by the "source" argument @@ -1440,7 +1448,8 @@ def getJSToNativeConversionTemplate(type, descriptorProvider, failureCode=None, isDefinitelyObject=False, isMember=False, isOptional=False, - invalidEnumValueFatal=True): + invalidEnumValueFatal=True, + defaultValue=None): """ Get a template for converting a JS value to a native object based on the given type and descriptor. If failureCode is given, then we're actually @@ -1461,6 +1470,12 @@ def getJSToNativeConversionTemplate(type, descriptorProvider, failureCode=None, If isOptional is true, then we are doing conversion of an optional argument with no default value. + invalidEnumValueFatal controls whether an invalid enum value conversion + attempt will throw (if true) or simply return without doing anything (if + false). + + If defaultValue is not None, it's the IDL default value for this conversion + The return value from this function is a tuple consisting of four things: 1) A string representing the conversion code. This will have template @@ -1470,6 +1485,9 @@ def getJSToNativeConversionTemplate(type, descriptorProvider, failureCode=None, ${valPtr} is a pointer to the JS::Value in question ${holderName} replaced by the holder's name, if any ${declName} replaced by the declaration's name + ${haveValue} replaced by an expression that evaluates to a boolean + for whether we have a JS::Value. Only used when + defaultValue is not None. 2) A CGThing representing the native C++ type we're converting to (declType). This is allowed to be None if the conversion code is @@ -1488,6 +1506,12 @@ def getJSToNativeConversionTemplate(type, descriptorProvider, failureCode=None, If holderType is not None then ${holderName} must be in scope before the generated code is entered. """ + # If we have a defaultValue then we're not actually optional for + # purposes of what we need to be declared as. + assert(defaultValue is None or not isOptional) + + # Also, we should not have a defaultValue if we know we're an object + assert(not isDefinitelyObject or defaultValue is None) # A helper function for dealing with failures due to the JS value being the # wrong type of value @@ -1497,6 +1521,29 @@ def getJSToNativeConversionTemplate(type, descriptorProvider, failureCode=None, "return Throw<%s>(cx, NS_ERROR_XPC_BAD_CONVERT_JS);" % toStringBool(isWorker)), post="\n") + # A helper function for handling default values. Takes a template + # body and the C++ code to set the default value and wraps the + # given template body in handling for the default value. + def handleDefault(template, setDefault): + if defaultValue is None: + return template + return CGWrapper( + CGIndenter(CGGeneric(template)), + pre="if (${haveValue}) {\n", + post=("\n" + "} else {\n" + "%s;\n" + "}" % + CGIndenter(CGGeneric(setDefault)).define())).define() + + # A helper function for handling null default values. Much like + # handleDefault, but checks that the default value, if it exists, is null. + def handleDefaultNull(template, codeToSetNull): + if (defaultValue is not None and + not isinstance(defaultValue, IDLNullValue)): + raise TypeError("Can't handle non-null default value here") + return handleDefault(template, codeToSetNull) + # A helper function for wrapping up the template body for # possibly-nullable objecty stuff def wrapObjectTemplate(templateBody, isDefinitelyObject, type, @@ -1515,6 +1562,11 @@ def getJSToNativeConversionTemplate(type, descriptorProvider, failureCode=None, "} else {\n" + CGIndenter(onFailure(failureCode, isWorker)).define() + "}") + if type.nullable(): + templateBody = handleDefaultNull(templateBody, codeToSetNull) + else: + assert(defaultValue is None) + return templateBody if type.isArray(): @@ -1602,11 +1654,13 @@ for (uint32_t i = 0; i < length; ++i) { if isMember: raise TypeError("Can't handle unions as members, we have a " "holderType") - nullable = type.nullable(); if nullable: type = type.inner + assert(defaultValue is None or + (isinstance(defaultValue, IDLNullValue) and nullable)) + unionArgumentObj = "${holderName}" if isOptional or nullable: unionArgumentObj += ".ref()" @@ -1759,10 +1813,10 @@ for (uint32_t i = 0; i < length; ++i) { nonConstDecl = "const_cast<" + typeName + "& >(${declName})" typeName = "const " + typeName - def handleNull(templateBody, setToNullVar): - null = CGGeneric("if (${val}.isNullOrUndefined()) {\n" + def handleNull(templateBody, setToNullVar, extraConditionForNull=""): + null = CGGeneric("if (%s${val}.isNullOrUndefined()) {\n" " %s.SetNull();\n" - "}" % setToNullVar) + "}" % (extraConditionForNull, setToNullVar)) templateBody = CGWrapper(CGIndenter(templateBody), pre="{\n", post="\n}") return CGList([null, templateBody], " else ") @@ -1792,7 +1846,13 @@ for (uint32_t i = 0; i < length; ++i) { templateBody = CGList([constructHolder, templateBody], "\n") if nullable: - templateBody = handleNull(templateBody, mutableDecl) + if defaultValue: + assert(isinstance(defaultValue, IDLNullValue)) + valueMissing = "!(${haveValue}) || " + else: + valueMissing = "" + templateBody = handleNull(templateBody, mutableDecl, + extraConditionForNull=valueMissing) templateBody = CGList([constructDecl, templateBody], "\n") return templateBody.define(), declType, holderType, False @@ -1980,6 +2040,18 @@ for (uint32_t i = 0; i < length; ++i) { nullBehavior = "eStringify" undefinedBehavior = "eStringify" + if defaultValue is not None: + if not isinstance(defaultValue, IDLNullValue): + raise TypeError("Can't handle non-null default values for " + "strings yet") + if not type.nullable(): + raise TypeError("Null default value for non-nullable string") + val = "(${haveValue}) ? ${val} : JSVAL_NULL" + valPtr = "(${haveValue}) ? ${valPtr} : NULL" + else: + val = "${val}" + valPtr = "${valPtr}" + if isMember: # We have to make a copy, because our jsval may well not # live as long as our string needs to. @@ -1987,12 +2059,12 @@ for (uint32_t i = 0; i < length; ++i) { return ( "{\n" " FakeDependentString str;\n" - " if (!ConvertJSValueToString(cx, ${val}, ${valPtr}, %s, %s, str)) {\n" + " if (!ConvertJSValueToString(cx, %s, %s, %s, %s, str)) {\n" " return false;\n" " }\n" " ${declName} = str;\n" "}\n" % - (nullBehavior, undefinedBehavior), + (val, valPtr, nullBehavior, undefinedBehavior), declType, None, isOptional) @@ -2001,16 +2073,19 @@ for (uint32_t i = 0; i < length; ++i) { else: declType = "NonNull" return ( - "if (!ConvertJSValueToString(cx, ${val}, ${valPtr}, %s, %s, ${holderName})) {\n" + "if (!ConvertJSValueToString(cx, %s, %s, %s, %s, ${holderName})) {\n" " return false;\n" "}\n" "const_cast<%s&>(${declName}) = &${holderName};" % - (nullBehavior, undefinedBehavior, declType), + (val, valPtr, nullBehavior, undefinedBehavior, declType), CGGeneric("const " + declType), CGGeneric("FakeDependentString"), # No need to deal with Optional here; we have handled it already False) if type.isEnum(): + if defaultValue is not None: + raise TypeError("Can't handle default values for enums") + if type.nullable(): raise TypeError("We don't support nullable enumerated arguments " "yet") @@ -2044,18 +2119,26 @@ for (uint32_t i = 0; i < length; ++i) { "rooting issues") # XXXbz we're going to assume that callback types are always # nullable and always have [TreatNonCallableAsNull] for now. + haveCallable = "${val}.isObject() && JS_ObjectIsCallable(cx, &${val}.toObject())" + if defaultValue is not None: + assert(isinstance(defaultValue, IDLNullValue)) + haveCallable = "${haveValue} && " + haveCallable return ( - "if (${val}.isObject() && JS_ObjectIsCallable(cx, &${val}.toObject())) {\n" + "if (%s) {\n" " ${declName} = &${val}.toObject();\n" "} else {\n" " ${declName} = NULL;\n" - "}", CGGeneric("JSObject*"), None, isOptional) + "}" % haveCallable, + CGGeneric("JSObject*"), None, isOptional) if type.isAny(): if isMember: raise TypeError("Can't handle member 'any'; need to sort out " "rooting issues") - return ("${declName} = ${val};", CGGeneric("JS::Value"), None, isOptional) + templateBody = "${declName} = ${val};" + templateBody = handleDefaultNull(templateBody, + "${declName} = JS::NullValue()") + return (templateBody, CGGeneric("JS::Value"), None, isOptional) if type.isObject(): if isMember: @@ -2093,9 +2176,17 @@ for (uint32_t i = 0; i < length; ++i) { declType = CGWrapper(declType, pre="const ") selfRef = "const_cast<%s&>(%s)" % (typeName, selfRef) - template = ("if (!%s.Init(cx, ${val})) {\n" + # We do manual default value handling here, because we + # actually do want a jsval, and we only handle null anyway + if defaultValue is not None: + assert(isinstance(defaultValue, IDLNullValue)) + val = "(${haveValue}) ? ${val} : JSVAL_NULL" + else: + val = "${val}" + + template = ("if (!%s.Init(cx, %s)) {\n" " return false;\n" - "}" % selfRef) + "}" % (selfRef, val)) return (template, declType, None, False) @@ -2105,15 +2196,43 @@ for (uint32_t i = 0; i < length; ++i) { # XXXbz need to add support for [EnforceRange] and [Clamp] typeName = builtinNames[type.tag()] if type.nullable(): - return ("if (${val}.isNullOrUndefined()) {\n" - " ${declName}.SetNull();\n" - "} else if (!ValueToPrimitive<" + typeName + ">(cx, ${val}, &${declName}.SetValue())) {\n" - " return false;\n" - "}", CGGeneric("Nullable<" + typeName + ">"), None, isOptional) + dataLoc = "${declName}.SetValue()" + nullCondition = "${val}.isNullOrUndefined()" + if defaultValue is not None and isinstance(defaultValue, IDLNullValue): + nullCondition = "!(${haveValue}) || " + nullCondition + template = ( + "if (%s) {\n" + " ${declName}.SetNull();\n" + "} else if (!ValueToPrimitive<%s>(cx, ${val}, &%s)) {\n" + " return false;\n" + "}" % (nullCondition, typeName, dataLoc)) + declType = CGGeneric("Nullable<" + typeName + ">") else: - return ("if (!ValueToPrimitive<" + typeName + ">(cx, ${val}, &${declName})) {\n" - " return false;\n" - "}", CGGeneric(typeName), None, isOptional) + assert(defaultValue is None or + not isinstance(defaultValue, IDLNullValue)) + dataLoc = "${declName}" + template = ( + "if (!ValueToPrimitive<%s>(cx, ${val}, &%s)) {\n" + " return false;\n" + "}" % (typeName, dataLoc)) + declType = CGGeneric(typeName) + if (defaultValue is not None and + # We already handled IDLNullValue, so just deal with the other ones + not isinstance(defaultValue, IDLNullValue)): + tag = defaultValue.type.tag() + if tag in numericTags: + defaultStr = defaultValue.value + else: + assert(tag == IDLType.Tags.bool) + defaultStr = toStringBool(defaultValue.value) + template = CGWrapper(CGIndenter(CGGeneric(template)), + pre="if (${haveValue}) {\n", + post=("\n" + "} else {\n" + " %s = %s;\n" + "}" % (dataLoc, defaultStr))).define() + + return (template, declType, None, isOptional) def instantiateJSToNativeConversionTemplate(templateTuple, replacements, argcAndIndex=None): @@ -2216,14 +2335,6 @@ def convertConstIDLValueToJSVal(value): return "DOUBLE_TO_JSVAL(%s)" % (value.value) raise TypeError("Const value of unhandled type: " + value.type) -def convertIDLDefaultValueToJSVal(value): - if value.type: - tag = value.type.tag() - if (tag == IDLType.Tags.domstring and - (not value.type.nullable() or not isinstance(value, IDLNullValue))): - assert False # Not implemented! - return convertConstIDLValueToJSVal(value) - class CGArgumentConverter(CGThing): """ A class that takes an IDL argument object, its index in the @@ -2237,8 +2348,8 @@ class CGArgumentConverter(CGThing): if argument.variadic: raise TypeError("We don't support variadic arguments yet " + str(argument.location)) - # XXXbz should optional jsval args get JSVAL_VOID? What about - # others? + assert(not argument.defaultValue or argument.optional) + replacer = { "index" : index, "argc" : argc, @@ -2248,20 +2359,14 @@ class CGArgumentConverter(CGThing): "declName" : "arg%d" % index, "holderName" : ("arg%d" % index) + "_holder" } - if argument.optional and argument.defaultValue: - replacer["defaultValue"] = convertIDLDefaultValueToJSVal(argument.defaultValue) - self.replacementVariables["val"] = string.Template( - "(${index} < ${argc} ? ${argv}[${index}] : ${defaultValue})" - ).substitute(replacer) - self.replacementVariables["valPtr"] = string.Template( - "(${index} < ${argc} ? &${argv}[${index}] : NULL)" - ).substitute(replacer) - else: - self.replacementVariables["val"] = string.Template( - "${argv}[${index}]" - ).substitute(replacer) - self.replacementVariables["valPtr"] = ( - "&" + self.replacementVariables["val"]) + self.replacementVariables["val"] = string.Template( + "${argv}[${index}]" + ).substitute(replacer) + self.replacementVariables["valPtr"] = ( + "&" + self.replacementVariables["val"]) + if argument.defaultValue: + self.replacementVariables["haveValue"] = string.Template( + "${index} < ${argc}").substitute(replacer) self.descriptorProvider = descriptorProvider if self.argument.optional and not self.argument.defaultValue: self.argcAndIndex = replacer @@ -2274,7 +2379,8 @@ class CGArgumentConverter(CGThing): getJSToNativeConversionTemplate(self.argument.type, self.descriptorProvider, isOptional=(self.argcAndIndex is not None), - invalidEnumValueFatal=self.invalidEnumValueFatal), + invalidEnumValueFatal=self.invalidEnumValueFatal, + defaultValue=self.argument.defaultValue), self.replacementVariables, self.argcAndIndex).define() @@ -3051,6 +3157,7 @@ class FakeArgument(): self.type = type self.optional = False self.variadic = False + self.defaultValue = None class CGSetterCall(CGGetterSetterCall): """ @@ -4067,7 +4174,8 @@ class CGDictionary(CGThing): getJSToNativeConversionTemplate(member.type, descriptorProvider, isMember=True, - isOptional=(not member.defaultValue))) + isOptional=(not member.defaultValue), + defaultValue=member.defaultValue)) for member in dictionary.members ] except NoSuchDescriptorError, err: if not self.workers: @@ -4204,6 +4312,8 @@ class CGDictionary(CGThing): assert holderType is None if dealWithOptional: replacements["declName"] = "(" + replacements["declName"] + ".Value())" + if member.defaultValue: + replacements["haveValue"] = "found" conversionReplacements = { "propId" : self.makeIdName(member.identifier.name), @@ -4221,12 +4331,8 @@ class CGDictionary(CGThing): " if (!JS_GetPropertyById(cx, &val.toObject(), ${propId}, &temp)) {\n" " return false;\n" " }\n" - "} else {\n" - " temp = ${defaultVal};\n" "}\n" "${convert}") - conversionReplacements["defaultVal"] = ( - convertIDLDefaultValueToJSVal(member.defaultValue)) else: conversion += ( "if (found) {\n" diff --git a/dom/bindings/parser/WebIDL.py b/dom/bindings/parser/WebIDL.py index 3c35c7adfe5..66ae5897d8b 100644 --- a/dom/bindings/parser/WebIDL.py +++ b/dom/bindings/parser/WebIDL.py @@ -1753,7 +1753,8 @@ class IDLNullValue(IDLObject): def coerceToType(self, type, location): if (not isinstance(type, IDLNullableType) and not (type.isUnion() and type.hasNullableType) and - not type.isDictionary()): + not type.isDictionary() and + not type.isAny()): raise WebIDLError("Cannot coerce null value to type %s." % type, [location]) diff --git a/dom/bindings/test/TestBindingHeader.h b/dom/bindings/test/TestBindingHeader.h index 0c0fec6d30d..2194064cb15 100644 --- a/dom/bindings/test/TestBindingHeader.h +++ b/dom/bindings/test/TestBindingHeader.h @@ -361,6 +361,7 @@ public: // Any types void PassAny(JSContext*, JS::Value, ErrorResult&); void PassOptionalAny(JSContext*, const Optional&, ErrorResult&); + void PassAnyDefaultNull(JSContext*, JS::Value, ErrorResult&); JS::Value ReceiveAny(JSContext*, ErrorResult&); // object types diff --git a/dom/bindings/test/TestCodeGen.webidl b/dom/bindings/test/TestCodeGen.webidl index 2bbd3f10cd9..dbd9e272f3d 100644 --- a/dom/bindings/test/TestCodeGen.webidl +++ b/dom/bindings/test/TestCodeGen.webidl @@ -51,49 +51,49 @@ interface TestInterface { void passShort(short arg); short receiveShort(); void passOptionalShort(optional short arg); - void passOptionalShortWithDefault(optional short arg = 0); + void passOptionalShortWithDefault(optional short arg = 5); readonly attribute long readonlyLong; attribute long writableLong; void passLong(long arg); long receiveLong(); void passOptionalLong(optional long arg); - void passOptionalLongWithDefault(optional long arg = 0); + void passOptionalLongWithDefault(optional long arg = 7); readonly attribute long long readonlyLongLong; attribute long long writableLongLong; void passLongLong(long long arg); long long receiveLongLong(); void passOptionalLongLong(optional long long arg); - void passOptionalLongLongWithDefault(optional long long arg = 0); + void passOptionalLongLongWithDefault(optional long long arg = -12); readonly attribute octet readonlyOctet; attribute octet writableOctet; void passOctet(octet arg); octet receiveOctet(); void passOptionalOctet(optional octet arg); - void passOptionalOctetWithDefault(optional octet arg = 0); + void passOptionalOctetWithDefault(optional octet arg = 19); readonly attribute unsigned short readonlyUnsignedShort; attribute unsigned short writableUnsignedShort; void passUnsignedShort(unsigned short arg); unsigned short receiveUnsignedShort(); void passOptionalUnsignedShort(optional unsigned short arg); - void passOptionalUnsignedShortWithDefault(optional unsigned short arg = 0); + void passOptionalUnsignedShortWithDefault(optional unsigned short arg = 2); readonly attribute unsigned long readonlyUnsignedLong; attribute unsigned long writableUnsignedLong; void passUnsignedLong(unsigned long arg); unsigned long receiveUnsignedLong(); void passOptionalUnsignedLong(optional unsigned long arg); - void passOptionalUnsignedLongWithDefault(optional unsigned long arg = 0); + void passOptionalUnsignedLongWithDefault(optional unsigned long arg = 6); readonly attribute unsigned long long readonlyUnsignedLongLong; attribute unsigned long long writableUnsignedLongLong; void passUnsignedLongLong(unsigned long long arg); unsigned long long receiveUnsignedLongLong(); void passOptionalUnsignedLongLong(optional unsigned long long arg); - void passOptionalUnsignedLongLongWithDefault(optional unsigned long long arg = 0); + void passOptionalUnsignedLongLongWithDefault(optional unsigned long long arg = 17); // Castable interface types // XXXbz add tests for infallible versions of all the castable interface stuff @@ -259,6 +259,7 @@ interface TestInterface { // Any types void passAny(any arg); void passOptionalAny(optional any arg); + void passAnyDefaultNull(optional any arg = null); any receiveAny(); // object types From d7233f1cb35c36d1cfa7676c5b133cc258f563f9 Mon Sep 17 00:00:00 2001 From: Boris Zbarsky Date: Tue, 31 Jul 2012 00:22:23 -0400 Subject: [PATCH 11/18] Bug 771636 part 2. Implement default values for WebIDL enums. r=peterv --- dom/bindings/Codegen.py | 16 ++++++++++------ dom/bindings/parser/WebIDL.py | 23 +++++++++++++---------- dom/bindings/parser/tests/test_enum.py | 20 ++++++++++++++++++++ dom/bindings/test/TestBindingHeader.h | 1 + dom/bindings/test/TestCodeGen.webidl | 2 ++ 5 files changed, 46 insertions(+), 16 deletions(-) diff --git a/dom/bindings/Codegen.py b/dom/bindings/Codegen.py index 32ae9452896..45c518e33a6 100644 --- a/dom/bindings/Codegen.py +++ b/dom/bindings/Codegen.py @@ -2083,9 +2083,6 @@ for (uint32_t i = 0; i < length; ++i) { False) if type.isEnum(): - if defaultValue is not None: - raise TypeError("Can't handle default values for enums") - if type.nullable(): raise TypeError("We don't support nullable enumerated arguments " "yet") @@ -2098,7 +2095,7 @@ for (uint32_t i = 0; i < length; ++i) { " return true;\n" " }\n") - return ( + template = ( "{\n" " bool ok;\n" " int index = FindEnumStringIndex<%(invalidEnumValueFatal)s>(cx, ${val}, %(values)s, \"%(enumtype)s\", &ok);\n" @@ -2110,8 +2107,15 @@ for (uint32_t i = 0; i < length; ++i) { "}" % { "enumtype" : enum, "values" : enum + "Values::strings", "invalidEnumValueFatal" : toStringBool(invalidEnumValueFatal), - "handleInvalidEnumValueCode" : handleInvalidEnumValueCode }, - CGGeneric(enum), None, isOptional) + "handleInvalidEnumValueCode" : handleInvalidEnumValueCode }) + + if defaultValue is not None: + assert(defaultValue.type.tag() == IDLType.Tags.domstring) + template = handleDefault(template, + ("${declName} = %sValues::%s" % + (enum, + getEnumValueName(defaultValue.value)))) + return (template, CGGeneric(enum), None, isOptional) if type.isCallback(): if isMember: diff --git a/dom/bindings/parser/WebIDL.py b/dom/bindings/parser/WebIDL.py index 66ae5897d8b..06881d412ee 100644 --- a/dom/bindings/parser/WebIDL.py +++ b/dom/bindings/parser/WebIDL.py @@ -1725,11 +1725,7 @@ class IDLValue(IDLObject): return IDLValue(self.location, type, innerValue.value) # Else, see if we can coerce to 'type'. - if self.type.isInteger(): - if not type.isInteger(): - raise WebIDLError("Cannot coerce type %s to type %s." % - (self.type, type), [location]) - + if self.type.isInteger() and type.isInteger(): # We're both integer types. See if we fit. (min, max) = integerTypeSizes[type._typeTag] @@ -1739,10 +1735,16 @@ class IDLValue(IDLObject): else: raise WebIDLError("Value %s is out of range for type %s." % (self.value, type), [location]) + elif self.type.isString() and type.isEnum(): + # Just keep our string, but make sure it's a valid value for this enum + if self.value not in type.inner.values(): + raise WebIDLError("'%s' is not a valid default value for enum %s" + % (self.value, type.inner.identifier.name), + [location, type.inner.location]) + return self else: - pass - - assert False # Not implemented! + raise WebIDLError("Cannot coerce type %s to type %s." % + (self.type, type), [location]) class IDLNullValue(IDLObject): def __init__(self, location): @@ -2767,8 +2769,9 @@ class Parser(Tokenizer): """ ConstValue : STRING """ - assert False - pass + location = self.getLocation(p, 1) + stringType = BuiltinTypes[IDLBuiltinType.Types.domstring] + p[0] = IDLValue(location, stringType, p[1]) def p_ConstValueNull(self, p): """ diff --git a/dom/bindings/parser/tests/test_enum.py b/dom/bindings/parser/tests/test_enum.py index 069c7f336a7..69a6932062d 100644 --- a/dom/bindings/parser/tests/test_enum.py +++ b/dom/bindings/parser/tests/test_enum.py @@ -59,3 +59,23 @@ def WebIDLTest(parser, harness): harness.check(attr.identifier.name, "foo", "Attr has correct name") harness.check(str(attr.type), "TestEnum (Wrapper)", "Attr type is the correct name") + + # Now reset our parser + parser = parser.reset() + threw = False + try: + parser.parse(""" + enum Enum { + "a", + "b", + "c" + }; + interface TestInterface { + void foo(optional Enum e = "d"); + }; + """) + results = parser.finish() + except: + threw = True + + harness.ok(threw, "Should not allow a bogus default value for an enum") diff --git a/dom/bindings/test/TestBindingHeader.h b/dom/bindings/test/TestBindingHeader.h index 2194064cb15..7c19b509826 100644 --- a/dom/bindings/test/TestBindingHeader.h +++ b/dom/bindings/test/TestBindingHeader.h @@ -341,6 +341,7 @@ public: // Enumarated types void PassEnum(TestEnum, ErrorResult&); void PassOptionalEnum(const Optional&, ErrorResult&); + void PassEnumWithDefault(TestEnum, ErrorResult&); TestEnum ReceiveEnum(ErrorResult&); TestEnum GetEnumAttribute(ErrorResult&); TestEnum GetReadonlyEnumAttribute(ErrorResult&); diff --git a/dom/bindings/test/TestCodeGen.webidl b/dom/bindings/test/TestCodeGen.webidl index dbd9e272f3d..a7e2f3dcc93 100644 --- a/dom/bindings/test/TestCodeGen.webidl +++ b/dom/bindings/test/TestCodeGen.webidl @@ -241,6 +241,7 @@ interface TestInterface { // No support for nullable enums yet // void passNullableEnum(TestEnum? arg); void passOptionalEnum(optional TestEnum arg); + void passEnumWithDefault(optional TestEnum arg = "a"); // void passOptionalNullableEnum(optional TestEnum? arg); // void passOptionalNullableEnumWithDefaultValue(optional TestEnum? arg = null); TestEnum receiveEnum(); @@ -355,6 +356,7 @@ dictionary Dict : ParentDict { long b = 8; long z = 9; DOMString str; + TestEnum otherEnum = "b"; }; dictionary ParentDict : GrandparentDict { From 43cfe7de051b0d3975e5ca49da21c663316b71fc Mon Sep 17 00:00:00 2001 From: Boris Zbarsky Date: Tue, 31 Jul 2012 00:22:23 -0400 Subject: [PATCH 12/18] Bug 771636 part 3. Implement default values for WebIDL strings. r=peterv --- dom/bindings/BindingUtils.h | 8 ++--- dom/bindings/Codegen.py | 46 ++++++++++++++------------- dom/bindings/test/TestBindingHeader.h | 2 ++ dom/bindings/test/TestCodeGen.webidl | 3 ++ 4 files changed, 31 insertions(+), 28 deletions(-) diff --git a/dom/bindings/BindingUtils.h b/dom/bindings/BindingUtils.h index 61ed5de6afc..13f292bac9b 100644 --- a/dom/bindings/BindingUtils.h +++ b/dom/bindings/BindingUtils.h @@ -857,6 +857,7 @@ enum StringificationBehavior { eNull }; +// pval must not be null and must point to a rooted JS::Value static inline bool ConvertJSValueToString(JSContext* cx, const JS::Value& v, JS::Value* pval, StringificationBehavior nullBehavior, @@ -876,12 +877,7 @@ ConvertJSValueToString(JSContext* cx, const JS::Value& v, JS::Value* pval, behavior = eStringify; } - // If pval is null, that means the argument was optional and - // not passed; turn those into void strings if they're - // supposed to be stringified. - if (behavior != eStringify || !pval) { - // Here behavior == eStringify implies !pval, so both eNull and - // eStringify should end up with void strings. + if (behavior != eStringify) { if (behavior == eEmpty) { result.Truncate(); } else { diff --git a/dom/bindings/Codegen.py b/dom/bindings/Codegen.py index 45c518e33a6..cc78cbcc4e4 100644 --- a/dom/bindings/Codegen.py +++ b/dom/bindings/Codegen.py @@ -2040,17 +2040,24 @@ for (uint32_t i = 0; i < length; ++i) { nullBehavior = "eStringify" undefinedBehavior = "eStringify" - if defaultValue is not None: - if not isinstance(defaultValue, IDLNullValue): - raise TypeError("Can't handle non-null default values for " - "strings yet") - if not type.nullable(): - raise TypeError("Null default value for non-nullable string") - val = "(${haveValue}) ? ${val} : JSVAL_NULL" - valPtr = "(${haveValue}) ? ${valPtr} : NULL" - else: - val = "${val}" - valPtr = "${valPtr}" + def getConversionCode(varName): + conversionCode = ( + "if (!ConvertJSValueToString(cx, ${val}, ${valPtr}, %s, %s, %s)) {\n" + " return false;\n" + "}" % (nullBehavior, undefinedBehavior, varName)) + if defaultValue is None: + return conversionCode + + if isinstance(defaultValue, IDLNullValue): + assert(type.nullable()) + return handleDefault(conversionCode, + "%s.SetNull()" % varName) + return handleDefault( + conversionCode, + ("static const PRUnichar data[] = { %s, 0 };\n" + "%s.SetData(data, ArrayLength(data) - 1)" % + (", ".join("'" + char + "'" for char in defaultValue.value), + varName))) if isMember: # We have to make a copy, because our jsval may well not @@ -2059,25 +2066,20 @@ for (uint32_t i = 0; i < length; ++i) { return ( "{\n" " FakeDependentString str;\n" - " if (!ConvertJSValueToString(cx, %s, %s, %s, %s, str)) {\n" - " return false;\n" - " }\n" + "%s\n" " ${declName} = str;\n" - "}\n" % - (val, valPtr, nullBehavior, undefinedBehavior), - declType, None, - isOptional) + "}\n" % CGIndenter(CGGeneric(getConversionCode("str"))).define(), + declType, None, isOptional) if isOptional: declType = "Optional" else: declType = "NonNull" + return ( - "if (!ConvertJSValueToString(cx, %s, %s, %s, %s, ${holderName})) {\n" - " return false;\n" - "}\n" + "%s\n" "const_cast<%s&>(${declName}) = &${holderName};" % - (val, valPtr, nullBehavior, undefinedBehavior, declType), + (getConversionCode("${holderName}"), declType), CGGeneric("const " + declType), CGGeneric("FakeDependentString"), # No need to deal with Optional here; we have handled it already False) diff --git a/dom/bindings/test/TestBindingHeader.h b/dom/bindings/test/TestBindingHeader.h index 7c19b509826..12964129137 100644 --- a/dom/bindings/test/TestBindingHeader.h +++ b/dom/bindings/test/TestBindingHeader.h @@ -335,6 +335,7 @@ public: void PassString(const nsAString&, ErrorResult&); void PassNullableString(const nsAString&, ErrorResult&); void PassOptionalString(const Optional&, ErrorResult&); + void PassOptionalStringWithDefaultValue(const nsAString&, ErrorResult&); void PassOptionalNullableString(const Optional&, ErrorResult&); void PassOptionalNullableStringWithDefaultValue(const nsAString&, ErrorResult&); @@ -551,6 +552,7 @@ private: void PassString(nsAString&, ErrorResult&) MOZ_DELETE; void PassNullableString(nsAString&, ErrorResult&) MOZ_DELETE; void PassOptionalString(Optional&, ErrorResult&) MOZ_DELETE; + void PassOptionalStringWithDefaultValue(nsAString&, ErrorResult&) MOZ_DELETE; void PassOptionalNullableString(Optional&, ErrorResult&) MOZ_DELETE; void PassOptionalNullableStringWithDefaultValue(nsAString&, ErrorResult&) MOZ_DELETE; diff --git a/dom/bindings/test/TestCodeGen.webidl b/dom/bindings/test/TestCodeGen.webidl index a7e2f3dcc93..6f37ef67d5f 100644 --- a/dom/bindings/test/TestCodeGen.webidl +++ b/dom/bindings/test/TestCodeGen.webidl @@ -233,6 +233,7 @@ interface TestInterface { void passString(DOMString arg); void passNullableString(DOMString? arg); void passOptionalString(optional DOMString arg); + void passOptionalStringWithDefaultValue(optional DOMString arg = "abc"); void passOptionalNullableString(optional DOMString? arg); void passOptionalNullableStringWithDefaultValue(optional DOMString? arg = null); @@ -357,6 +358,8 @@ dictionary Dict : ParentDict { long z = 9; DOMString str; TestEnum otherEnum = "b"; + DOMString otherStr = "def"; + DOMString? yetAnotherStr = null; }; dictionary ParentDict : GrandparentDict { From 01f8e81e315d6914f570b262de847633cf721a9e Mon Sep 17 00:00:00 2001 From: Mike Habicher Date: Mon, 30 Jul 2012 17:59:05 -0400 Subject: [PATCH 13/18] Bug 740997 - ICS camera support, r=jst,gal,roc --- b2g/installer/package-manifest.in | 1 + browser/installer/package-manifest.in | 1 + config/autoconf.mk.in | 1 + configure.in | 14 +- dom/Makefile.in | 1 + dom/base/Navigator.cpp | 44 +- dom/base/Navigator.h | 6 + dom/base/nsDOMClassInfo.cpp | 24 + dom/base/nsDOMClassInfoClasses.h | 4 + dom/camera/CameraCapabilities.h | 44 ++ dom/camera/CameraCommon.h | 68 ++ dom/camera/CameraControl.cpp | 486 ++++++++++++++ dom/camera/CameraControl.h | 438 +++++++++++++ dom/camera/CameraPreview.cpp | 98 +++ dom/camera/CameraPreview.h | 58 ++ dom/camera/DOMCameraManager.cpp | 89 +++ dom/camera/DOMCameraManager.h | 82 +++ dom/camera/FallbackCameraCapabilities.cpp | 140 ++++ dom/camera/FallbackCameraControl.cpp | 147 +++++ dom/camera/FallbackCameraManager.cpp | 22 + dom/camera/GonkCameraCapabilities.cpp | 352 ++++++++++ dom/camera/GonkCameraControl.cpp | 602 ++++++++++++++++++ dom/camera/GonkCameraControl.h | 76 +++ dom/camera/GonkCameraHwMgr.cpp | 435 +++++++++++++ dom/camera/GonkCameraHwMgr.h | 125 ++++ dom/camera/GonkCameraManager.cpp | 107 ++++ dom/camera/GonkCameraPreview.cpp | 197 ++++++ dom/camera/GonkCameraPreview.h | 59 ++ dom/camera/GonkNativeWindow.cpp | 475 ++++++++++++++ dom/camera/GonkNativeWindow.h | 192 ++++++ dom/camera/Makefile.in | 52 ++ dom/camera/nsIDOMCameraManager.idl | 341 ++++++++++ dom/camera/nsIDOMNavigatorCamera.idl | 15 + dom/dom-config.mk | 1 + .../mochitest/general/test_interfaces.html | 6 +- js/xpconnect/src/dictionary_helper_gen.conf | 7 +- layout/build/Makefile.in | 4 + mobile/xul/installer/package-manifest.in | 1 + 38 files changed, 4808 insertions(+), 7 deletions(-) create mode 100644 dom/camera/CameraCapabilities.h create mode 100644 dom/camera/CameraCommon.h create mode 100644 dom/camera/CameraControl.cpp create mode 100644 dom/camera/CameraControl.h create mode 100644 dom/camera/CameraPreview.cpp create mode 100644 dom/camera/CameraPreview.h create mode 100644 dom/camera/DOMCameraManager.cpp create mode 100644 dom/camera/DOMCameraManager.h create mode 100644 dom/camera/FallbackCameraCapabilities.cpp create mode 100644 dom/camera/FallbackCameraControl.cpp create mode 100644 dom/camera/FallbackCameraManager.cpp create mode 100644 dom/camera/GonkCameraCapabilities.cpp create mode 100644 dom/camera/GonkCameraControl.cpp create mode 100644 dom/camera/GonkCameraControl.h create mode 100644 dom/camera/GonkCameraHwMgr.cpp create mode 100644 dom/camera/GonkCameraHwMgr.h create mode 100644 dom/camera/GonkCameraManager.cpp create mode 100644 dom/camera/GonkCameraPreview.cpp create mode 100644 dom/camera/GonkCameraPreview.h create mode 100644 dom/camera/GonkNativeWindow.cpp create mode 100644 dom/camera/GonkNativeWindow.h create mode 100644 dom/camera/Makefile.in create mode 100644 dom/camera/nsIDOMCameraManager.idl create mode 100644 dom/camera/nsIDOMNavigatorCamera.idl diff --git a/b2g/installer/package-manifest.in b/b2g/installer/package-manifest.in index e1e25402bc7..4a0cdd2d8b0 100644 --- a/b2g/installer/package-manifest.in +++ b/b2g/installer/package-manifest.in @@ -165,6 +165,7 @@ #ifdef MOZ_B2G_BT @BINPATH@/components/dom_bluetooth.xpt #endif +@BINPATH@/components/dom_camera.xpt @BINPATH@/components/dom_canvas.xpt @BINPATH@/components/dom_contacts.xpt @BINPATH@/components/dom_alarm.xpt diff --git a/browser/installer/package-manifest.in b/browser/installer/package-manifest.in index bf417b0ff03..b74c323d3a2 100644 --- a/browser/installer/package-manifest.in +++ b/browser/installer/package-manifest.in @@ -174,6 +174,7 @@ #ifdef MOZ_B2G_BT @BINPATH@/components/dom_bluetooth.xpt #endif +@BINPATH@/components/dom_camera.xpt @BINPATH@/components/dom_canvas.xpt @BINPATH@/components/dom_contacts.xpt @BINPATH@/components/dom_alarm.xpt diff --git a/config/autoconf.mk.in b/config/autoconf.mk.in index 7b713036a6e..5e0ad3919e1 100644 --- a/config/autoconf.mk.in +++ b/config/autoconf.mk.in @@ -273,6 +273,7 @@ MOZ_NATIVE_NSS = @MOZ_NATIVE_NSS@ MOZ_B2G_RIL = @MOZ_B2G_RIL@ MOZ_B2G_BT = @MOZ_B2G_BT@ +MOZ_B2G_CAMERA = @MOZ_B2G_CAMERA@ MOZ_SYS_MSG = @MOZ_SYS_MSG@ diff --git a/configure.in b/configure.in index c190251a20e..5214ac23ac5 100644 --- a/configure.in +++ b/configure.in @@ -192,7 +192,7 @@ if test -n "$gonkdir" ; then ;; esac - CPPFLAGS="-DANDROID -isystem $gonkdir/bionic/libc/$ARCH_DIR/include -isystem $gonkdir/bionic/libc/include/ -isystem $gonkdir/bionic/libc/kernel/common -isystem $gonkdir/bionic/libc/kernel/$ARCH_DIR -isystem $gonkdir/bionic/libm/include -I$gonkdir/frameworks/base/opengl/include -I$gonkdir/frameworks/base/native/include -I$gonkdir/hardware/libhardware/include -I$gonkdir/hardware/libhardware_legacy/include -I$gonkdir/system -I$gonkdir/system/core/include -isystem $gonkdir/bionic -I$gonkdir/frameworks/base/include -I$gonkdir/external/dbus $CPPFLAGS -I$gonkdir/frameworks/base/services/sensorservice" + CPPFLAGS="-DANDROID -isystem $gonkdir/bionic/libc/$ARCH_DIR/include -isystem $gonkdir/bionic/libc/include/ -isystem $gonkdir/bionic/libc/kernel/common -isystem $gonkdir/bionic/libc/kernel/$ARCH_DIR -isystem $gonkdir/bionic/libm/include -I$gonkdir/frameworks/base/opengl/include -I$gonkdir/frameworks/base/native/include -I$gonkdir/hardware/libhardware/include -I$gonkdir/hardware/libhardware_legacy/include -I$gonkdir/system -I$gonkdir/system/core/include -isystem $gonkdir/bionic -I$gonkdir/frameworks/base/include -I$gonkdir/external/dbus $CPPFLAGS -I$gonkdir/frameworks/base/services/sensorservice -I$gonkdir/frameworks/base/services/camera" CFLAGS="-mandroid -fno-short-enums -fno-exceptions $CFLAGS" CXXFLAGS="-mandroid -fno-short-enums -fno-exceptions $CXXFLAGS $STLPORT_CPPFLAGS" LIBS="$LIBS $STLPORT_LIBS" @@ -7351,6 +7351,18 @@ if test -n "$MOZ_SYS_MSG"; then fi AC_SUBST(MOZ_SYS_MSG) +dnl ======================================================== +dnl = Enable Camera Interface for B2G (Gonk usually) +dnl ======================================================== +MOZ_ARG_ENABLE_BOOL(b2g-camera, +[ --enable-b2g-camera Set compile flags necessary for compiling camera API for B2G ], + MOZ_B2G_CAMERA=1, + MOZ_B2G_CAMERA= ) +if test -n "$MOZ_B2G_CAMERA"; then + AC_DEFINE(MOZ_B2G_CAMERA) +fi +AC_SUBST(MOZ_B2G_CAMERA) + dnl ======================================================== dnl = Support for demangling undefined symbols dnl ======================================================== diff --git a/dom/Makefile.in b/dom/Makefile.in index 9247ef3225e..3d98a8f8ff3 100644 --- a/dom/Makefile.in +++ b/dom/Makefile.in @@ -70,6 +70,7 @@ DIRS += \ ipc \ identity \ workers \ + camera \ $(NULL) ifdef MOZ_B2G_RIL diff --git a/dom/base/Navigator.cpp b/dom/base/Navigator.cpp index 2565b9f29a0..cae943bd6c1 100644 --- a/dom/base/Navigator.cpp +++ b/dom/base/Navigator.cpp @@ -50,6 +50,8 @@ #include "nsIDOMBluetoothManager.h" #include "BluetoothManager.h" #endif +#include "nsIDOMCameraManager.h" +#include "DOMCameraManager.h" #include "nsIDOMGlobalPropertyInitializer.h" @@ -112,6 +114,7 @@ NS_INTERFACE_MAP_BEGIN(Navigator) #ifdef MOZ_B2G_BT NS_INTERFACE_MAP_ENTRY(nsIDOMNavigatorBluetooth) #endif + NS_INTERFACE_MAP_ENTRY(nsIDOMNavigatorCamera) NS_INTERFACE_MAP_ENTRY(nsIDOMNavigatorSystemMessages) NS_DOM_INTERFACE_MAP_ENTRY_CLASSINFO(Navigator) NS_INTERFACE_MAP_END @@ -181,6 +184,8 @@ Navigator::Invalidate() } #endif + mCameraManager = nullptr; + #ifdef MOZ_SYS_MSG if (mMessagesManager) { mMessagesManager = nullptr; @@ -1320,6 +1325,30 @@ Navigator::MozSetMessageHandler(const nsAString& aType, #endif } +//***************************************************************************** +// nsNavigator::nsIDOMNavigatorCamera +//***************************************************************************** + +NS_IMETHODIMP +Navigator::GetMozCameras(nsIDOMCameraManager** aCameraManager) +{ + if (!mCameraManager) { + nsCOMPtr win = do_QueryReferent(mWindow); + NS_ENSURE_TRUE(win, NS_ERROR_FAILURE); + + if (!win->GetOuterWindow() || win->GetOuterWindow()->GetCurrentInnerWindow() != win) { + return NS_ERROR_NOT_AVAILABLE; + } + + mCameraManager = nsDOMCameraManager::Create(win->WindowID()); + } + + nsRefPtr cameraManager = mCameraManager; + cameraManager.forget(aCameraManager); + + return NS_OK; +} + size_t Navigator::SizeOfIncludingThis(nsMallocSizeOfFun aMallocSizeOf) const { @@ -1344,12 +1373,19 @@ Navigator::SetWindow(nsPIDOMWindow *aInnerWindow) void Navigator::OnNavigation() { - // Inform MediaManager in case there are live streams or pending callbacks. -#ifdef MOZ_MEDIA_NAVIGATOR - MediaManager *manager = MediaManager::Get(); nsCOMPtr win = do_QueryReferent(mWindow); - return manager->OnNavigation(win->WindowID()); + if (!win) { + return; + } + +#ifdef MOZ_MEDIA_NAVIGATOR + // Inform MediaManager in case there are live streams or pending callbacks. + MediaManager *manager = MediaManager::Get(); + manager->OnNavigation(win->WindowID()); #endif + if (mCameraManager) { + mCameraManager->OnNavigation(win->WindowID()); + } } } // namespace dom diff --git a/dom/base/Navigator.h b/dom/base/Navigator.h index cc810bfb891..508d78c8c51 100644 --- a/dom/base/Navigator.h +++ b/dom/base/Navigator.h @@ -41,6 +41,9 @@ class nsIDOMMozVoicemail; #include "nsIDOMNavigatorSystemMessages.h" +#include "nsIDOMNavigatorCamera.h" +#include "DOMCameraManager.h" + //***************************************************************************** // Navigator: Script "navigator" object //***************************************************************************** @@ -82,6 +85,7 @@ class Navigator : public nsIDOMNavigator #ifdef MOZ_B2G_BT , public nsIDOMNavigatorBluetooth #endif + , public nsIDOMNavigatorCamera , public nsIDOMNavigatorSystemMessages { public: @@ -134,6 +138,7 @@ public: // Helper to initialize mMessagesManager. nsresult EnsureMessagesManager(); #endif + NS_DECL_NSIDOMNAVIGATORCAMERA private: bool IsSmsAllowed() const; @@ -155,6 +160,7 @@ private: #ifdef MOZ_B2G_BT nsCOMPtr mBluetooth; #endif + nsRefPtr mCameraManager; nsCOMPtr mMessagesManager; nsWeakPtr mWindow; }; diff --git a/dom/base/nsDOMClassInfo.cpp b/dom/base/nsDOMClassInfo.cpp index 63c01c3f13c..a4fabe7fe4b 100644 --- a/dom/base/nsDOMClassInfo.cpp +++ b/dom/base/nsDOMClassInfo.cpp @@ -522,6 +522,10 @@ using mozilla::dom::indexedDB::IDBWrapperCache; #include "mozilla/dom/Activity.h" +#include "DOMCameraManager.h" +#include "CameraControl.h" +#include "CameraCapabilities.h" + #include "DOMError.h" #include "DOMRequest.h" #include "nsIOpenWindowEventDetail.h" @@ -1668,6 +1672,13 @@ static nsDOMClassInfoData sClassInfoData[] = { DOM_DEFAULT_SCRIPTABLE_FLAGS) #endif + NS_DEFINE_CLASSINFO_DATA(CameraManager, nsDOMGenericSH, + DOM_DEFAULT_SCRIPTABLE_FLAGS) + NS_DEFINE_CLASSINFO_DATA(CameraControl, nsDOMGenericSH, + DOM_DEFAULT_SCRIPTABLE_FLAGS) + NS_DEFINE_CLASSINFO_DATA(CameraCapabilities, nsDOMGenericSH, + DOM_DEFAULT_SCRIPTABLE_FLAGS) + NS_DEFINE_CLASSINFO_DATA(DOMError, nsDOMGenericSH, DOM_DEFAULT_SCRIPTABLE_FLAGS) @@ -2466,6 +2477,7 @@ nsDOMClassInfo::Init() #ifdef MOZ_B2G_BT DOM_CLASSINFO_MAP_ENTRY(nsIDOMNavigatorBluetooth) #endif + DOM_CLASSINFO_MAP_ENTRY(nsIDOMNavigatorCamera) DOM_CLASSINFO_MAP_ENTRY(nsIDOMNavigatorSystemMessages) DOM_CLASSINFO_MAP_END @@ -4457,6 +4469,18 @@ nsDOMClassInfo::Init() DOM_CLASSINFO_MAP_END #endif + DOM_CLASSINFO_MAP_BEGIN(CameraManager, nsIDOMCameraManager) + DOM_CLASSINFO_MAP_ENTRY(nsIDOMCameraManager) + DOM_CLASSINFO_MAP_END + + DOM_CLASSINFO_MAP_BEGIN(CameraControl, nsICameraControl) + DOM_CLASSINFO_MAP_ENTRY(nsICameraControl) + DOM_CLASSINFO_MAP_END + + DOM_CLASSINFO_MAP_BEGIN(CameraCapabilities, nsICameraCapabilities) + DOM_CLASSINFO_MAP_ENTRY(nsICameraCapabilities) + DOM_CLASSINFO_MAP_END + DOM_CLASSINFO_MAP_BEGIN(DOMError, nsIDOMDOMError) DOM_CLASSINFO_MAP_ENTRY(nsIDOMDOMError) DOM_CLASSINFO_MAP_END diff --git a/dom/base/nsDOMClassInfoClasses.h b/dom/base/nsDOMClassInfoClasses.h index 78bf3fb6942..f416c57f321 100644 --- a/dom/base/nsDOMClassInfoClasses.h +++ b/dom/base/nsDOMClassInfoClasses.h @@ -526,6 +526,10 @@ DOMCI_CLASS(BluetoothDevice) DOMCI_CLASS(BluetoothDeviceEvent) #endif +DOMCI_CLASS(CameraManager) +DOMCI_CLASS(CameraControl) +DOMCI_CLASS(CameraCapabilities) + DOMCI_CLASS(DOMError) DOMCI_CLASS(DOMRequest) DOMCI_CLASS(OpenWindowEventDetail) diff --git a/dom/camera/CameraCapabilities.h b/dom/camera/CameraCapabilities.h new file mode 100644 index 00000000000..c2e529065e8 --- /dev/null +++ b/dom/camera/CameraCapabilities.h @@ -0,0 +1,44 @@ +/* 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/. */ + +#ifndef DOM_CAMERA_NSCAMERACAPABILITIES_H +#define DOM_CAMERA_NSCAMERACAPABILITIES_H + +#include "CameraControl.h" +#include "nsAutoPtr.h" + +namespace mozilla { + +typedef nsresult (*ParseItemAndAddFunc)(JSContext* aCx, JSObject* aArray, PRUint32 aIndex, const char* aStart, char** aEnd); + +class nsCameraCapabilities : public nsICameraCapabilities +{ +public: + NS_DECL_ISUPPORTS + NS_DECL_NSICAMERACAPABILITIES + + nsCameraCapabilities(nsCameraControl* aCamera); + + nsresult ParameterListToNewArray( + JSContext* cx, + JSObject** aArray, + PRUint32 aKey, + ParseItemAndAddFunc aParseItemAndAdd + ); + nsresult StringListToNewObject(JSContext* aCx, JS::Value* aArray, PRUint32 aKey); + nsresult DimensionListToNewObject(JSContext* aCx, JS::Value* aArray, PRUint32 aKey); + +private: + nsCameraCapabilities(const nsCameraCapabilities&) MOZ_DELETE; + nsCameraCapabilities& operator=(const nsCameraCapabilities&) MOZ_DELETE; + +protected: + /* additional members */ + ~nsCameraCapabilities(); + nsCOMPtr mCamera; +}; + +} // namespace mozilla + +#endif // DOM_CAMERA_NSCAMERACAPABILITIES_H diff --git a/dom/camera/CameraCommon.h b/dom/camera/CameraCommon.h new file mode 100644 index 00000000000..c9493d0911e --- /dev/null +++ b/dom/camera/CameraCommon.h @@ -0,0 +1,68 @@ +/* -*- Mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 40 -*- */ +/* vim: set ts=2 et sw=2 tw=40: */ +/* 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/. */ + +#ifndef DOM_CAMERA_CAMERACOMMON_H +#define DOM_CAMERA_CAMERACOMMON_H + +#ifndef __func__ +#ifdef __FUNCTION__ +#define __func__ __FUNCTION__ +#else +#define __func__ __FILE__ +#endif +#endif + +#ifndef NAN +#define NAN std::numeric_limits::quiet_NaN() +#endif + +#include "nsThreadUtils.h" +#include "nsIDOMCameraManager.h" + +#define DOM_CAMERA_LOG( l, ... ) \ + do { \ + if ( DOM_CAMERA_LOG_LEVEL >= (l) ) { \ + printf_stderr (__VA_ARGS__); \ + } \ + } while (0) + +#define DOM_CAMERA_LOGA( ... ) DOM_CAMERA_LOG( 0, __VA_ARGS__ ) + +enum { + DOM_CAMERA_LOG_NOTHING, + DOM_CAMERA_LOG_ERROR, + DOM_CAMERA_LOG_WARNING, + DOM_CAMERA_LOG_INFO +}; + +#define DOM_CAMERA_LOGI( ... ) DOM_CAMERA_LOG( DOM_CAMERA_LOG_INFO, __VA_ARGS__ ) +#define DOM_CAMERA_LOGW( ... ) DOM_CAMERA_LOG( DOM_CAMERA_LOG_WARNING, __VA_ARGS__ ) +#define DOM_CAMERA_LOGE( ... ) DOM_CAMERA_LOG( DOM_CAMERA_LOG_ERROR, __VA_ARGS__ ) + +class CameraErrorResult : public nsRunnable +{ +public: + CameraErrorResult(nsICameraErrorCallback* onError, const nsString& aErrorMsg) + : mOnErrorCb(onError) + , mErrorMsg(aErrorMsg) + { } + + NS_IMETHOD Run() + { + MOZ_ASSERT(NS_IsMainThread()); + + if (mOnErrorCb) { + mOnErrorCb->HandleEvent(mErrorMsg); + } + return NS_OK; + } + +protected: + nsCOMPtr mOnErrorCb; + const nsString mErrorMsg; +}; + +#endif // DOM_CAMERA_CAMERACOMMON_H diff --git a/dom/camera/CameraControl.cpp b/dom/camera/CameraControl.cpp new file mode 100644 index 00000000000..02a09dd1f82 --- /dev/null +++ b/dom/camera/CameraControl.cpp @@ -0,0 +1,486 @@ +/* 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 "nsCOMPtr.h" +#include "nsDOMClassInfo.h" +#include "jsapi.h" +#include "nsThread.h" +#include "DOMCameraManager.h" +#include "CameraControl.h" +#include "CameraCapabilities.h" +#include "CameraControl.h" + +#define DOM_CAMERA_LOG_LEVEL 3 +#include "CameraCommon.h" + +using namespace mozilla; +using namespace dom; + +DOMCI_DATA(CameraControl, nsICameraControl) + +NS_INTERFACE_MAP_BEGIN(nsCameraControl) + NS_INTERFACE_MAP_ENTRY(nsISupports) + NS_INTERFACE_MAP_ENTRY(nsICameraControl) + NS_DOM_INTERFACE_MAP_ENTRY_CLASSINFO(CameraControl) +NS_INTERFACE_MAP_END + +NS_IMPL_ADDREF(nsCameraControl) +NS_IMPL_RELEASE(nsCameraControl) + +// Helpers for string properties. +nsresult +nsCameraControl::SetHelper(PRUint32 aKey, const nsAString& aValue) +{ + SetParameter(aKey, NS_ConvertUTF16toUTF8(aValue).get()); + return NS_OK; +} + +nsresult +nsCameraControl::GetHelper(PRUint32 aKey, nsAString& aValue) +{ + const char* value = GetParameterConstChar(aKey); + if (!value) { + return NS_ERROR_FAILURE; + } + + aValue.AssignASCII(value); + return NS_OK; +} + +// Helpers for doubles. +nsresult +nsCameraControl::SetHelper(PRUint32 aKey, double aValue) +{ + SetParameter(aKey, aValue); + return NS_OK; +} + +nsresult +nsCameraControl::GetHelper(PRUint32 aKey, double* aValue) +{ + MOZ_ASSERT(aValue); + *aValue = GetParameterDouble(aKey); + return NS_OK; +} + +// Helper for weighted regions. +nsresult +nsCameraControl::SetHelper(JSContext* aCx, PRUint32 aKey, const JS::Value& aValue, PRUint32 aLimit) +{ + if (aLimit == 0) { + DOM_CAMERA_LOGI("%s:%d : aLimit = 0, nothing to do\n", __func__, __LINE__); + return NS_OK; + } + + if (!aValue.isObject()) { + return NS_ERROR_INVALID_ARG; + } + + uint32_t length = 0; + + JSObject* regions = &aValue.toObject(); + if (!JS_GetArrayLength(aCx, regions, &length)) { + return NS_ERROR_FAILURE; + } + + DOM_CAMERA_LOGI("%s:%d : got %d regions (limited to %d)\n", __func__, __LINE__, length, aLimit); + if (length > aLimit) { + length = aLimit; + } + + nsTArray regionArray; + regionArray.SetCapacity(length); + + for (PRUint32 i = 0; i < length; ++i) { + JS::Value v; + + if (!JS_GetElement(aCx, regions, i, &v)) { + return NS_ERROR_FAILURE; + } + + CameraRegion* r = regionArray.AppendElement(); + /** + * These are the default values. We can remove these when the xpidl + * dictionary parser gains the ability to grok default values. + */ + r->top = -1000; + r->left = -1000; + r->bottom = 1000; + r->right = 1000; + r->weight = 1000; + + nsresult rv = r->Init(aCx, &v); + NS_ENSURE_SUCCESS(rv, rv); + + DOM_CAMERA_LOGI("region %d: top=%d, left=%d, bottom=%d, right=%d, weight=%d\n", + i, + r->top, + r->left, + r->bottom, + r->right, + r->weight + ); + } + SetParameter(aKey, regionArray); + return NS_OK; +} + +nsresult +nsCameraControl::GetHelper(JSContext* aCx, PRUint32 aKey, JS::Value* aValue) +{ + nsTArray regionArray; + + GetParameter(aKey, regionArray); + + JSObject* array = JS_NewArrayObject(aCx, 0, nullptr); + if (!array) { + return NS_ERROR_OUT_OF_MEMORY; + } + + PRUint32 length = regionArray.Length(); + DOM_CAMERA_LOGI("%s:%d : got %d regions\n", __func__, __LINE__, length); + + for (PRUint32 i = 0; i < length; ++i) { + CameraRegion* r = ®ionArray[i]; + JS::Value v; + + JSObject* o = JS_NewObject(aCx, nullptr, nullptr, nullptr); + if (!o) { + return NS_ERROR_OUT_OF_MEMORY; + } + + DOM_CAMERA_LOGI("top=%d\n", r->top); + v = INT_TO_JSVAL(r->top); + if (!JS_SetProperty(aCx, o, "top", &v)) { + return NS_ERROR_FAILURE; + } + DOM_CAMERA_LOGI("left=%d\n", r->left); + v = INT_TO_JSVAL(r->left); + if (!JS_SetProperty(aCx, o, "left", &v)) { + return NS_ERROR_FAILURE; + } + DOM_CAMERA_LOGI("bottom=%d\n", r->bottom); + v = INT_TO_JSVAL(r->bottom); + if (!JS_SetProperty(aCx, o, "bottom", &v)) { + return NS_ERROR_FAILURE; + } + DOM_CAMERA_LOGI("right=%d\n", r->right); + v = INT_TO_JSVAL(r->right); + if (!JS_SetProperty(aCx, o, "right", &v)) { + return NS_ERROR_FAILURE; + } + DOM_CAMERA_LOGI("weight=%d\n", r->weight); + v = INT_TO_JSVAL(r->weight); + if (!JS_SetProperty(aCx, o, "weight", &v)) { + return NS_ERROR_FAILURE; + } + + v = OBJECT_TO_JSVAL(o); + if (!JS_SetElement(aCx, array, i, &v)) { + return NS_ERROR_FAILURE; + } + } + + *aValue = JS::ObjectValue(*array); + return NS_OK; +} + +/* readonly attribute nsICameraCapabilities capabilities; */ +NS_IMETHODIMP +nsCameraControl::GetCapabilities(nsICameraCapabilities** aCapabilities) +{ + if (!mCapabilities) { + mCapabilities = new nsCameraCapabilities(this); + } + + nsCOMPtr capabilities = mCapabilities; + capabilities.forget(aCapabilities); + return NS_OK; +} + +/* attribute DOMString effect; */ +NS_IMETHODIMP +nsCameraControl::GetEffect(nsAString& aEffect) +{ + return GetHelper(CAMERA_PARAM_EFFECT, aEffect); +} +NS_IMETHODIMP +nsCameraControl::SetEffect(const nsAString& aEffect) +{ + return SetHelper(CAMERA_PARAM_EFFECT, aEffect); +} + +/* attribute DOMString whiteBalanceMode; */ +NS_IMETHODIMP +nsCameraControl::GetWhiteBalanceMode(nsAString& aWhiteBalanceMode) +{ + return GetHelper(CAMERA_PARAM_WHITEBALANCE, aWhiteBalanceMode); +} +NS_IMETHODIMP +nsCameraControl::SetWhiteBalanceMode(const nsAString& aWhiteBalanceMode) +{ + return SetHelper(CAMERA_PARAM_WHITEBALANCE, aWhiteBalanceMode); +} + +/* attribute DOMString sceneMode; */ +NS_IMETHODIMP +nsCameraControl::GetSceneMode(nsAString& aSceneMode) +{ + return GetHelper(CAMERA_PARAM_SCENEMODE, aSceneMode); +} +NS_IMETHODIMP +nsCameraControl::SetSceneMode(const nsAString& aSceneMode) +{ + return SetHelper(CAMERA_PARAM_SCENEMODE, aSceneMode); +} + +/* attribute DOMString flashMode; */ +NS_IMETHODIMP +nsCameraControl::GetFlashMode(nsAString& aFlashMode) +{ + return GetHelper(CAMERA_PARAM_FLASHMODE, aFlashMode); +} +NS_IMETHODIMP +nsCameraControl::SetFlashMode(const nsAString& aFlashMode) +{ + return SetHelper(CAMERA_PARAM_FLASHMODE, aFlashMode); +} + +/* attribute DOMString focusMode; */ +NS_IMETHODIMP +nsCameraControl::GetFocusMode(nsAString& aFocusMode) +{ + return GetHelper(CAMERA_PARAM_FOCUSMODE, aFocusMode); +} +NS_IMETHODIMP +nsCameraControl::SetFocusMode(const nsAString& aFocusMode) +{ + return SetHelper(CAMERA_PARAM_FOCUSMODE, aFocusMode); +} + +/* attribute double zoom; */ +NS_IMETHODIMP +nsCameraControl::GetZoom(double* aZoom) +{ + return GetHelper(CAMERA_PARAM_ZOOM, aZoom); +} +NS_IMETHODIMP +nsCameraControl::SetZoom(double aZoom) +{ + return SetHelper(CAMERA_PARAM_ZOOM, aZoom); +} + +/* attribute jsval meteringAreas; */ +NS_IMETHODIMP +nsCameraControl::GetMeteringAreas(JSContext* cx, JS::Value* aMeteringAreas) +{ + return GetHelper(cx, CAMERA_PARAM_METERINGAREAS, aMeteringAreas); +} +NS_IMETHODIMP +nsCameraControl::SetMeteringAreas(JSContext* cx, const JS::Value& aMeteringAreas) +{ + return SetHelper(cx, CAMERA_PARAM_METERINGAREAS, aMeteringAreas, mMaxMeteringAreas); +} + +/* attribute jsval focusAreas; */ +NS_IMETHODIMP +nsCameraControl::GetFocusAreas(JSContext* cx, JS::Value* aFocusAreas) +{ + return GetHelper(cx, CAMERA_PARAM_FOCUSAREAS, aFocusAreas); +} +NS_IMETHODIMP +nsCameraControl::SetFocusAreas(JSContext* cx, const JS::Value& aFocusAreas) +{ + return SetHelper(cx, CAMERA_PARAM_FOCUSAREAS, aFocusAreas, mMaxFocusAreas); +} + +/* readonly attribute double focalLength; */ +NS_IMETHODIMP +nsCameraControl::GetFocalLength(double* aFocalLength) +{ + return GetHelper(CAMERA_PARAM_FOCALLENGTH, aFocalLength); +} + +/* readonly attribute double focusDistanceNear; */ +NS_IMETHODIMP +nsCameraControl::GetFocusDistanceNear(double* aFocusDistanceNear) +{ + return GetHelper(CAMERA_PARAM_FOCUSDISTANCENEAR, aFocusDistanceNear); +} + +/* readonly attribute double focusDistanceOptimum; */ +NS_IMETHODIMP +nsCameraControl::GetFocusDistanceOptimum(double* aFocusDistanceOptimum) +{ + return GetHelper(CAMERA_PARAM_FOCUSDISTANCEOPTIMUM, aFocusDistanceOptimum); +} + +/* readonly attribute double focusDistanceFar; */ +NS_IMETHODIMP +nsCameraControl::GetFocusDistanceFar(double* aFocusDistanceFar) +{ + return GetHelper(CAMERA_PARAM_FOCUSDISTANCEFAR, aFocusDistanceFar); +} + +/* void setExposureCompensation (const JS::Value& aCompensation, JSContext* cx); */ +NS_IMETHODIMP +nsCameraControl::SetExposureCompensation(const JS::Value& aCompensation, JSContext* cx) +{ + if (aCompensation.isNullOrUndefined()) { + // use NaN to switch the camera back into auto mode + return SetHelper(CAMERA_PARAM_EXPOSURECOMPENSATION, NAN); + } + + double compensation; + if (!JS_ValueToNumber(cx, aCompensation, &compensation)) { + return NS_ERROR_INVALID_ARG; + } + + return SetHelper(CAMERA_PARAM_EXPOSURECOMPENSATION, compensation); +} + +/* readonly attribute double exposureCompensation; */ +NS_IMETHODIMP +nsCameraControl::GetExposureCompensation(double* aExposureCompensation) +{ + return GetHelper(CAMERA_PARAM_EXPOSURECOMPENSATION, aExposureCompensation); +} + +/* attribute nsICameraShutterCallback onShutter; */ +NS_IMETHODIMP +nsCameraControl::GetOnShutter(nsICameraShutterCallback** aOnShutter) +{ + *aOnShutter = mOnShutterCb; + return NS_OK; +} +NS_IMETHODIMP +nsCameraControl::SetOnShutter(nsICameraShutterCallback* aOnShutter) +{ + mOnShutterCb = aOnShutter; + return NS_OK; +} + +/* void startRecording (in jsval aOptions, in nsICameraStartRecordingCallback onSuccess, [optional] in nsICameraErrorCallback onError); */ +NS_IMETHODIMP +nsCameraControl::StartRecording(const JS::Value& aOptions, nsICameraStartRecordingCallback* onSuccess, nsICameraErrorCallback* onError, JSContext* cx) +{ + NS_ENSURE_TRUE(onSuccess, NS_ERROR_INVALID_ARG); + + CameraSize size; + nsresult rv = size.Init(cx, &aOptions); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr startRecordingTask = new StartRecordingTask(this, size, onSuccess, onError); + mCameraThread->Dispatch(startRecordingTask, NS_DISPATCH_NORMAL); + + return NS_OK; +} + +/* void stopRecording (); */ +NS_IMETHODIMP +nsCameraControl::StopRecording() +{ + nsCOMPtr stopRecordingTask = new StopRecordingTask(this); + mCameraThread->Dispatch(stopRecordingTask, NS_DISPATCH_NORMAL); + + return NS_OK; +} + +/* [implicit_jscontext] void getPreviewStream (in jsval aOptions, in nsICameraPreviewStreamCallback onSuccess, [optional] in nsICameraErrorCallback onError); */ +NS_IMETHODIMP +nsCameraControl::GetPreviewStream(const JS::Value& aOptions, nsICameraPreviewStreamCallback* onSuccess, nsICameraErrorCallback* onError, JSContext* cx) +{ + NS_ENSURE_TRUE(onSuccess, NS_ERROR_INVALID_ARG); + + CameraSize size; + nsresult rv = size.Init(cx, &aOptions); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr getPreviewStreamTask = new GetPreviewStreamTask(this, size, onSuccess, onError); + mCameraThread->Dispatch(getPreviewStreamTask, NS_DISPATCH_NORMAL); + + return NS_OK; +} + +/* void autoFocus (in nsICameraAutoFocusCallback onSuccess, [optional] in nsICameraErrorCallback onError); */ +NS_IMETHODIMP +nsCameraControl::AutoFocus(nsICameraAutoFocusCallback* onSuccess, nsICameraErrorCallback* onError) +{ + NS_ENSURE_TRUE(onSuccess, NS_ERROR_INVALID_ARG); + + nsCOMPtr autoFocusTask = new AutoFocusTask(this, onSuccess, onError); + mCameraThread->Dispatch(autoFocusTask, NS_DISPATCH_NORMAL); + + return NS_OK; +} + +/* void takePicture (in jsval aOptions, in nsICameraTakePictureCallback onSuccess, [optional] in nsICameraErrorCallback onError); */ +NS_IMETHODIMP nsCameraControl::TakePicture(const JS::Value& aOptions, nsICameraTakePictureCallback* onSuccess, nsICameraErrorCallback* onError, JSContext* cx) +{ + NS_ENSURE_TRUE(onSuccess, NS_ERROR_INVALID_ARG); + + CameraPictureOptions options; + CameraSize size; + CameraPosition pos; + + nsresult rv = options.Init(cx, &aOptions); + NS_ENSURE_SUCCESS(rv, rv); + + rv = size.Init(cx, &options.pictureSize); + NS_ENSURE_SUCCESS(rv, rv); + + /** + * 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; + rv = pos.Init(cx, &options.position); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr takePictureTask = new TakePictureTask(this, size, options.rotation, options.fileFormat, pos, onSuccess, onError); + mCameraThread->Dispatch(takePictureTask, NS_DISPATCH_NORMAL); + + return NS_OK; +} + +void +nsCameraControl::AutoFocusComplete(bool aSuccess) +{ + /** + * Auto focusing can change some of the camera's parameters, so + * we need to pull a new set before sending the result to the + * main thread. + */ + PullParametersImpl(nullptr); + + nsCOMPtr autoFocusResult = new AutoFocusResult(aSuccess, mAutoFocusOnSuccessCb); + + nsresult rv = NS_DispatchToMainThread(autoFocusResult); + if (NS_FAILED(rv)) { + NS_WARNING("Failed to dispatch autoFocus() onSuccess callback to main thread!"); + } +} + +void +nsCameraControl::TakePictureComplete(PRUint8* aData, PRUint32 aLength) +{ + PRUint8* data = new PRUint8[aLength]; + + memcpy(data, aData, aLength); + + /** + * TODO: pick up the actual specified picture format for the MIME type; + * for now, assume we'll be using JPEGs. + */ + nsIDOMBlob* blob = new nsDOMMemoryFile(static_cast(data), static_cast(aLength), NS_LITERAL_STRING("image/jpeg")); + nsCOMPtr takePictureResult = new TakePictureResult(blob, mTakePictureOnSuccessCb); + + nsresult rv = NS_DispatchToMainThread(takePictureResult); + if (NS_FAILED(rv)) { + NS_WARNING("Failed to dispatch takePicture() onSuccess callback to main thread!"); + } +} diff --git a/dom/camera/CameraControl.h b/dom/camera/CameraControl.h new file mode 100644 index 00000000000..8c5f6234bf3 --- /dev/null +++ b/dom/camera/CameraControl.h @@ -0,0 +1,438 @@ +/* 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/. */ + +#ifndef DOM_CAMERA_NSCAMERACONTROL_H +#define DOM_CAMERA_NSCAMERACONTROL_H + +#include "prtypes.h" +#include "nsCOMPtr.h" +#include "nsThread.h" +#include "nsDOMFile.h" +#include "DictionaryHelpers.h" +#include "CameraPreview.h" +#include "nsIDOMCameraManager.h" + +#define DOM_CAMERA_LOG_LEVEL 3 +#include "CameraCommon.h" + +namespace mozilla { + +using namespace dom; + +class GetPreviewStreamTask; +class AutoFocusTask; +class TakePictureTask; +class StartRecordingTask; +class StopRecordingTask; +class SetParameterTask; +class GetParameterTask; +class PushParametersTask; +class PullParametersTask; + +// Main camera control. +class nsCameraControl : public nsICameraControl +{ + friend class GetPreviewStreamTask; + friend class AutoFocusTask; + friend class TakePictureTask; + friend class StartRecordingTask; + friend class StopRecordingTask; + friend class SetParameterTask; + friend class GetParameterTask; + friend class PushParametersTask; + friend class PullParametersTask; + +public: + NS_DECL_ISUPPORTS + NS_DECL_NSICAMERACONTROL + + enum { + CAMERA_PARAM_EFFECT, + CAMERA_PARAM_WHITEBALANCE, + CAMERA_PARAM_SCENEMODE, + CAMERA_PARAM_FLASHMODE, + CAMERA_PARAM_FOCUSMODE, + CAMERA_PARAM_ZOOM, + CAMERA_PARAM_METERINGAREAS, + CAMERA_PARAM_FOCUSAREAS, + CAMERA_PARAM_FOCALLENGTH, + CAMERA_PARAM_FOCUSDISTANCENEAR, + CAMERA_PARAM_FOCUSDISTANCEOPTIMUM, + CAMERA_PARAM_FOCUSDISTANCEFAR, + CAMERA_PARAM_EXPOSURECOMPENSATION, + + CAMERA_PARAM_SUPPORTED_PREVIEWSIZES, + CAMERA_PARAM_SUPPORTED_VIDEOSIZES, + CAMERA_PARAM_SUPPORTED_PICTURESIZES, + CAMERA_PARAM_SUPPORTED_PICTUREFORMATS, + CAMERA_PARAM_SUPPORTED_WHITEBALANCES, + CAMERA_PARAM_SUPPORTED_SCENEMODES, + CAMERA_PARAM_SUPPORTED_EFFECTS, + CAMERA_PARAM_SUPPORTED_FLASHMODES, + CAMERA_PARAM_SUPPORTED_FOCUSMODES, + CAMERA_PARAM_SUPPORTED_MAXFOCUSAREAS, + CAMERA_PARAM_SUPPORTED_MAXMETERINGAREAS, + CAMERA_PARAM_SUPPORTED_MINEXPOSURECOMPENSATION, + CAMERA_PARAM_SUPPORTED_MAXEXPOSURECOMPENSATION, + CAMERA_PARAM_SUPPORTED_EXPOSURECOMPENSATIONSTEP, + CAMERA_PARAM_SUPPORTED_ZOOM, + CAMERA_PARAM_SUPPORTED_ZOOMRATIOS + }; + virtual const char* GetParameter(const char* aKey) = 0; + virtual const char* GetParameterConstChar(PRUint32 aKey) = 0; + virtual double GetParameterDouble(PRUint32 aKey) = 0; + virtual void GetParameter(PRUint32 aKey, nsTArray& aRegions) = 0; + virtual void SetParameter(const char* aKey, const char* aValue) = 0; + virtual void SetParameter(PRUint32 aKey, const char* aValue) = 0; + virtual void SetParameter(PRUint32 aKey, double aValue) = 0; + virtual void SetParameter(PRUint32 aKey, const nsTArray& aRegions) = 0; + virtual void PushParameters() = 0; + + nsCameraControl(PRUint32 aCameraId, nsIThread* aCameraThread) + : mCameraId(aCameraId) + , mCameraThread(aCameraThread) + , mCapabilities(nullptr) + , mPreview(nullptr) + , mFileFormat() + , mMaxMeteringAreas(0) + , mMaxFocusAreas(0) + , mAutoFocusOnSuccessCb(nullptr) + , mAutoFocusOnErrorCb(nullptr) + , mTakePictureOnSuccessCb(nullptr) + , mTakePictureOnErrorCb(nullptr) + , mStartRecordingOnSuccessCb(nullptr) + , mStartRecordingOnErrorCb(nullptr) + , mOnShutterCb(nullptr) + { } + + void TakePictureComplete(PRUint8 *aData, PRUint32 aLength); + void AutoFocusComplete(bool aSuccess); + +protected: + virtual ~nsCameraControl() { } + + nsresult SetHelper(PRUint32 aKey, const nsAString& aValue); + nsresult GetHelper(PRUint32 aKey, nsAString& aValue); + nsresult SetHelper(PRUint32 aKey, double aValue); + nsresult GetHelper(PRUint32 aKey, double* aValue); + nsresult SetHelper(JSContext* aCx, PRUint32 aKey, const JS::Value& aValue, PRUint32 aLimit); + nsresult GetHelper(JSContext* aCx, PRUint32 aKey, JS::Value* aValue); + + virtual nsresult GetPreviewStreamImpl(GetPreviewStreamTask* aGetPreviewStream) = 0; + virtual nsresult AutoFocusImpl(AutoFocusTask* aAutoFocus) = 0; + virtual nsresult TakePictureImpl(TakePictureTask* aTakePicture) = 0; + virtual nsresult StartRecordingImpl(StartRecordingTask* aStartRecording) = 0; + virtual nsresult StopRecordingImpl(StopRecordingTask* aStopRecording) = 0; + virtual nsresult PushParametersImpl(PushParametersTask* aPushParameters) = 0; + virtual nsresult PullParametersImpl(PullParametersTask* aPullParameters) = 0; + +private: + nsCameraControl(const nsCameraControl&) MOZ_DELETE; + nsCameraControl& operator=(const nsCameraControl&) MOZ_DELETE; + +protected: + /* additional members */ + PRUint32 mCameraId; + nsCOMPtr mCameraThread; + nsCOMPtr mCapabilities; + PRUint32 mPreviewWidth; + PRUint32 mPreviewHeight; + nsCOMPtr mPreview; + nsString mFileFormat; + PRUint32 mMaxMeteringAreas; + PRUint32 mMaxFocusAreas; + + nsCOMPtr mAutoFocusOnSuccessCb; + nsCOMPtr mAutoFocusOnErrorCb; + nsCOMPtr mTakePictureOnSuccessCb; + nsCOMPtr mTakePictureOnErrorCb; + nsCOMPtr mStartRecordingOnSuccessCb; + nsCOMPtr mStartRecordingOnErrorCb; + nsCOMPtr mOnShutterCb; +}; + +// Return the resulting preview stream to JS. Runs on the main thread. +class GetPreviewStreamResult : public nsRunnable +{ +public: + GetPreviewStreamResult(nsIDOMMediaStream* aStream, nsICameraPreviewStreamCallback* onSuccess) + : mStream(aStream) + , mOnSuccessCb(onSuccess) + { } + + NS_IMETHOD Run() + { + MOZ_ASSERT(NS_IsMainThread()); + + if (mOnSuccessCb) { + mOnSuccessCb->HandleEvent(mStream); + } + return NS_OK; + } + +protected: + nsCOMPtr mStream; + nsCOMPtr mOnSuccessCb; +}; + +// Get the desired preview stream. +class GetPreviewStreamTask : public nsRunnable +{ +public: + GetPreviewStreamTask(nsCameraControl* aCameraControl, CameraSize aSize, nsICameraPreviewStreamCallback* onSuccess, nsICameraErrorCallback* onError) + : mSize(aSize) + , mCameraControl(aCameraControl) + , mOnSuccessCb(onSuccess) + , mOnErrorCb(onError) + { } + + NS_IMETHOD Run() + { + nsresult rv = mCameraControl->GetPreviewStreamImpl(this); + + if (NS_FAILED(rv) && mOnErrorCb) { + rv = NS_DispatchToMainThread(new CameraErrorResult(mOnErrorCb, NS_LITERAL_STRING("FAILURE"))); + NS_ENSURE_SUCCESS(rv, rv); + } + return rv; + } + + CameraSize mSize; + nsCOMPtr mCameraControl; + nsCOMPtr mOnSuccessCb; + nsCOMPtr mOnErrorCb; +}; + +// Return the autofocus status to JS. Runs on the main thread. +class AutoFocusResult : public nsRunnable +{ +public: + AutoFocusResult(bool aSuccess, nsICameraAutoFocusCallback* onSuccess) + : mSuccess(aSuccess) + , mOnSuccessCb(onSuccess) + { } + + NS_IMETHOD Run() + { + MOZ_ASSERT(NS_IsMainThread()); + + if (mOnSuccessCb) { + mOnSuccessCb->HandleEvent(mSuccess); + } + return NS_OK; + } + +protected: + bool mSuccess; + nsCOMPtr mOnSuccessCb; +}; + +// Autofocus the camera. +class AutoFocusTask : public nsRunnable +{ +public: + AutoFocusTask(nsCameraControl* aCameraControl, nsICameraAutoFocusCallback* onSuccess, nsICameraErrorCallback* onError) + : mCameraControl(aCameraControl) + , mOnSuccessCb(onSuccess) + , mOnErrorCb(onError) + { } + + NS_IMETHOD Run() + { + DOM_CAMERA_LOGI("%s:%d\n", __func__, __LINE__); + nsresult rv = mCameraControl->AutoFocusImpl(this); + DOM_CAMERA_LOGI("%s:%d\n", __func__, __LINE__); + + if (NS_FAILED(rv) && mOnErrorCb) { + rv = NS_DispatchToMainThread(new CameraErrorResult(mOnErrorCb, NS_LITERAL_STRING("FAILURE"))); + NS_ENSURE_SUCCESS(rv, rv); + } + return rv; + } + + nsCOMPtr mCameraControl; + nsCOMPtr mOnSuccessCb; + nsCOMPtr mOnErrorCb; +}; + +// Return the captured picture to JS. Runs on the main thread. +class TakePictureResult : public nsRunnable +{ +public: + TakePictureResult(nsIDOMBlob* aImage, nsICameraTakePictureCallback* onSuccess) + : mImage(aImage) + , mOnSuccessCb(onSuccess) + { } + + NS_IMETHOD Run() + { + MOZ_ASSERT(NS_IsMainThread()); + + if (mOnSuccessCb) { + mOnSuccessCb->HandleEvent(mImage); + } + return NS_OK; + } + +protected: + nsCOMPtr mImage; + nsCOMPtr mOnSuccessCb; +}; + +// Capture a still image with the camera. +class TakePictureTask : public nsRunnable +{ +public: + TakePictureTask(nsCameraControl* aCameraControl, CameraSize aSize, PRInt32 aRotation, const nsAString& aFileFormat, CameraPosition aPosition, nsICameraTakePictureCallback* onSuccess, nsICameraErrorCallback* onError) + : mCameraControl(aCameraControl) + , mSize(aSize) + , mRotation(aRotation) + , mFileFormat(aFileFormat) + , mPosition(aPosition) + , mOnSuccessCb(onSuccess) + , mOnErrorCb(onError) + { } + + NS_IMETHOD Run() + { + DOM_CAMERA_LOGI("%s:%d\n", __func__, __LINE__); + nsresult rv = mCameraControl->TakePictureImpl(this); + DOM_CAMERA_LOGI("%s:%d\n", __func__, __LINE__); + + if (NS_FAILED(rv) && mOnErrorCb) { + rv = NS_DispatchToMainThread(new CameraErrorResult(mOnErrorCb, NS_LITERAL_STRING("FAILURE"))); + NS_ENSURE_SUCCESS(rv, rv); + } + return rv; + } + + nsCOMPtr mCameraControl; + CameraSize mSize; + PRInt32 mRotation; + nsString mFileFormat; + CameraPosition mPosition; + nsCOMPtr mOnSuccessCb; + nsCOMPtr mOnErrorCb; +}; + +// Return the captured video to JS. Runs on the main thread. +class StartRecordingResult : public nsRunnable +{ +public: + StartRecordingResult(nsIDOMMediaStream* aStream, nsICameraStartRecordingCallback* onSuccess) + : mStream(aStream) + , mOnSuccessCb(onSuccess) + { } + + NS_IMETHOD Run() + { + MOZ_ASSERT(NS_IsMainThread()); + + if (mOnSuccessCb) { + mOnSuccessCb->HandleEvent(mStream); + } + return NS_OK; + } + +protected: + nsCOMPtr mStream; + nsCOMPtr mOnSuccessCb; +}; + +// Start video recording. +class StartRecordingTask : public nsRunnable +{ +public: + StartRecordingTask(nsCameraControl* aCameraControl, CameraSize aSize, nsICameraStartRecordingCallback* onSuccess, nsICameraErrorCallback* onError) + : mSize(aSize) + , mCameraControl(aCameraControl) + , mOnSuccessCb(onSuccess) + , mOnErrorCb(onError) + { } + + NS_IMETHOD Run() + { + DOM_CAMERA_LOGI("%s:%d\n", __func__, __LINE__); + nsresult rv = mCameraControl->StartRecordingImpl(this); + DOM_CAMERA_LOGI("%s:%d\n", __func__, __LINE__); + + if (NS_FAILED(rv) && mOnErrorCb) { + rv = NS_DispatchToMainThread(new CameraErrorResult(mOnErrorCb, NS_LITERAL_STRING("FAILURE"))); + NS_ENSURE_SUCCESS(rv, rv); + } + return rv; + } + + CameraSize mSize; + nsCOMPtr mCameraControl; + nsCOMPtr mOnSuccessCb; + nsCOMPtr mOnErrorCb; +}; + +// Stop video recording. +class StopRecordingTask : public nsRunnable +{ +public: + StopRecordingTask(nsCameraControl* aCameraControl) + : mCameraControl(aCameraControl) + { } + + NS_IMETHOD Run() + { + DOM_CAMERA_LOGI("%s:%d\n", __func__, __LINE__); + nsresult rv = mCameraControl->StopRecordingImpl(this); + DOM_CAMERA_LOGI("%s:%d\n", __func__, __LINE__); + + NS_ENSURE_SUCCESS(rv, rv); + return NS_OK; + } + + nsCOMPtr mCameraControl; +}; + +// Pushes all camera parameters to the camera. +class PushParametersTask : public nsRunnable +{ +public: + PushParametersTask(nsCameraControl* aCameraControl) + : mCameraControl(aCameraControl) + { } + + NS_IMETHOD Run() + { + DOM_CAMERA_LOGI("%s:%d\n", __func__, __LINE__); + nsresult rv = mCameraControl->PushParametersImpl(this); + DOM_CAMERA_LOGI("%s:%d\n", __func__, __LINE__); + + NS_ENSURE_SUCCESS(rv, rv); + return NS_OK; + } + + nsCOMPtr mCameraControl; +}; + +// Get all camera parameters from the camera. +class PullParametersTask : public nsRunnable +{ +public: + PullParametersTask(nsCameraControl* aCameraControl) + : mCameraControl(aCameraControl) + { } + + NS_IMETHOD Run() + { + DOM_CAMERA_LOGI("%s:%d\n", __func__, __LINE__); + nsresult rv = mCameraControl->PullParametersImpl(this); + DOM_CAMERA_LOGI("%s:%d\n", __func__, __LINE__); + + NS_ENSURE_SUCCESS(rv, rv); + return NS_OK; + } + + nsCOMPtr mCameraControl; +}; + +} // namespace mozilla + +#endif // DOM_CAMERA_NSCAMERACONTROL_H diff --git a/dom/camera/CameraPreview.cpp b/dom/camera/CameraPreview.cpp new file mode 100644 index 00000000000..bfebf27e25c --- /dev/null +++ b/dom/camera/CameraPreview.cpp @@ -0,0 +1,98 @@ +/* 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 "CameraPreview.h" + +#define DOM_CAMERA_LOG_LEVEL 3 +#include "CameraCommon.h" + +using namespace mozilla; + +NS_IMPL_ISUPPORTS1(CameraPreview, CameraPreview) + +class CameraPreviewListener : public MediaStreamListener +{ +public: + CameraPreviewListener(CameraPreview* aPreview) : + mPreview(aPreview) + { + DOM_CAMERA_LOGI("%s:%d : this=%p\n", __func__, __LINE__, this); + } + + ~CameraPreviewListener() + { + DOM_CAMERA_LOGI("%s:%d : this=%p\n", __func__, __LINE__, this); + } + + void NotifyConsumptionChanged(MediaStreamGraph* aGraph, Consumption aConsuming) + { + const char* state; + + DOM_CAMERA_LOGI("%s:%d : this=%p\n", __func__, __LINE__, this); + + switch (aConsuming) { + case NOT_CONSUMED: + state = "not consuming"; + break; + + case CONSUMED: + state = "consuming"; + break; + + default: + state = "unknown"; + break; + } + + DOM_CAMERA_LOGA("camera viewfinder is %s\n", state); + + switch (aConsuming) { + case NOT_CONSUMED: + mPreview->Stop(); + break; + + case CONSUMED: + mPreview->Start(); + break; + } + } + +protected: + nsCOMPtr mPreview; +}; + +CameraPreview::CameraPreview(PRUint32 aWidth, PRUint32 aHeight) + : nsDOMMediaStream() + , mWidth(aWidth) + , mHeight(aHeight) + , mFramesPerSecond(0) + , mFrameCount(0) +{ + DOM_CAMERA_LOGI("%s:%d : mWidth=%d, mHeight=%d : this=%p\n", __func__, __LINE__, mWidth, mHeight, this); + + mImageContainer = LayerManager::CreateImageContainer(); + MediaStreamGraph* gm = MediaStreamGraph::GetInstance(); + mStream = gm->CreateInputStream(this); + mInput = GetStream()->AsSourceStream(); + mInput->AddListener(new CameraPreviewListener(this)); +} + +void +CameraPreview::SetFrameRate(PRUint32 aFramesPerSecond) +{ + mFramesPerSecond = aFramesPerSecond; + mInput->AddTrack(TRACK_VIDEO, mFramesPerSecond, 0, new VideoSegment()); + mInput->AdvanceKnownTracksTime(MEDIA_TIME_MAX); +} + +CameraPreview::~CameraPreview() +{ + DOM_CAMERA_LOGI("%s:%d : this=%p\n", __func__, __LINE__, this); + + /** + * We _must_ remember to call RemoveListener on this before destroying this, + * else the media framework will trigger a double-free. + */ + DOM_CAMERA_LOGI("%s:%d : this=%p\n", __func__, __LINE__, this); +} diff --git a/dom/camera/CameraPreview.h b/dom/camera/CameraPreview.h new file mode 100644 index 00000000000..f68481ffc16 --- /dev/null +++ b/dom/camera/CameraPreview.h @@ -0,0 +1,58 @@ +/* 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/. */ + +#ifndef DOM_CAMERA_CAMERAPREVIEW_H +#define DOM_CAMERA_CAMERAPREVIEW_H + +#include "MediaStreamGraph.h" +#include "StreamBuffer.h" +#include "nsDOMMediaStream.h" + +#define DOM_CAMERA_LOG_LEVEL 3 +#include "CameraCommon.h" + +using namespace mozilla; +using namespace mozilla::layers; + +namespace mozilla { + +class CameraPreview : public nsDOMMediaStream + , public MediaStreamListener +{ +public: + NS_DECL_ISUPPORTS + + CameraPreview(PRUint32 aWidth, PRUint32 aHeight); + + void SetFrameRate(PRUint32 aFramesPerSecond); + + NS_IMETHODIMP + GetCurrentTime(double* aCurrentTime) { + return nsDOMMediaStream::GetCurrentTime(aCurrentTime); + } + + virtual void Start() = 0; + virtual void Stop() = 0; + +protected: + virtual ~CameraPreview(); + + PRUint32 mWidth; + PRUint32 mHeight; + PRUint32 mFramesPerSecond; + SourceMediaStream* mInput; + nsRefPtr mImageContainer; + VideoSegment mVideoSegment; + PRUint32 mFrameCount; + + enum { TRACK_VIDEO = 1 }; + +private: + CameraPreview(const CameraPreview&) MOZ_DELETE; + CameraPreview& operator=(const CameraPreview&) MOZ_DELETE; +}; + +} // namespace mozilla + +#endif // DOM_CAMERA_CAMERAPREVIEW_H diff --git a/dom/camera/DOMCameraManager.cpp b/dom/camera/DOMCameraManager.cpp new file mode 100644 index 00000000000..3075c7ca667 --- /dev/null +++ b/dom/camera/DOMCameraManager.cpp @@ -0,0 +1,89 @@ +/* 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 "CameraControl.h" +#include "DOMCameraManager.h" +#include "nsDOMClassInfo.h" +#include "DictionaryHelpers.h" + +#define DOM_CAMERA_LOG_LEVEL 3 +#include "CameraCommon.h" + +using namespace mozilla; + +DOMCI_DATA(CameraManager, nsIDOMCameraManager) + +NS_INTERFACE_MAP_BEGIN(nsDOMCameraManager) + NS_INTERFACE_MAP_ENTRY(nsISupports) + NS_INTERFACE_MAP_ENTRY(nsIDOMCameraManager) + NS_DOM_INTERFACE_MAP_ENTRY_CLASSINFO(CameraManager) +NS_INTERFACE_MAP_END + +NS_IMPL_ADDREF(nsDOMCameraManager) +NS_IMPL_RELEASE(nsDOMCameraManager) + +/** + * nsDOMCameraManager::GetListOfCameras + * is implementation-specific, and can be found in (e.g.) + * GonkCameraManager.cpp and FallbackCameraManager.cpp. + */ + +nsDOMCameraManager::nsDOMCameraManager(PRUint64 aWindowId) + : mWindowId(aWindowId) +{ + /* member initializers and constructor code */ + DOM_CAMERA_LOGI("%s:%d\n", __func__, __LINE__); +} + +nsDOMCameraManager::~nsDOMCameraManager() +{ + /* destructor code */ + DOM_CAMERA_LOGI("%s:%d\n", __func__, __LINE__); +} + +void +nsDOMCameraManager::OnNavigation(PRUint64 aWindowId) +{ + // TODO: implement -- see getUserMedia() implementation +} + +// static creator +already_AddRefed +nsDOMCameraManager::Create(PRUint64 aWindowId) +{ + // TODO: check for permissions here to access cameras + + nsRefPtr cameraManager = new nsDOMCameraManager(aWindowId); + return cameraManager.forget(); +} + +/* [implicit_jscontext] void getCamera ([optional] in jsval aOptions, in nsICameraGetCameraCallback onSuccess, [optional] in nsICameraErrorCallback onError); */ +NS_IMETHODIMP +nsDOMCameraManager::GetCamera(const JS::Value& aOptions, nsICameraGetCameraCallback* onSuccess, nsICameraErrorCallback* onError, JSContext* cx) +{ + NS_ENSURE_TRUE(onSuccess, NS_ERROR_INVALID_ARG); + + PRUint32 cameraId = 0; // back (or forward-facing) camera by default + CameraSelector selector; + + nsresult rv = selector.Init(cx, &aOptions); + NS_ENSURE_SUCCESS(rv, rv); + + if (selector.camera.EqualsASCII("front")) { + cameraId = 1; + } + + // reuse the same camera thread to conserve resources + if (!mCameraThread) { + rv = NS_NewThread(getter_AddRefs(mCameraThread)); + NS_ENSURE_SUCCESS(rv, rv); + } + + DOM_CAMERA_LOGI("%s:%d\n", __func__, __LINE__); + + nsCOMPtr getCameraTask = new GetCameraTask(cameraId, onSuccess, onError, mCameraThread); + mCameraThread->Dispatch(getCameraTask, NS_DISPATCH_NORMAL); + + return NS_OK; +} diff --git a/dom/camera/DOMCameraManager.h b/dom/camera/DOMCameraManager.h new file mode 100644 index 00000000000..86699c23bc3 --- /dev/null +++ b/dom/camera/DOMCameraManager.h @@ -0,0 +1,82 @@ +/* -*- Mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 40 -*- */ +/* vim: set ts=2 et sw=2 tw=40: */ +/* 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/. */ + +#ifndef DOM_CAMERA_DOMCAMERAMANAGER_H +#define DOM_CAMERA_DOMCAMERAMANAGER_H + +#include "nsCOMPtr.h" +#include "nsAutoPtr.h" +#include "nsIThread.h" +#include "nsThreadUtils.h" +#include "nsIDOMCameraManager.h" + +class nsDOMCameraManager : public nsIDOMCameraManager +{ +public: + NS_DECL_ISUPPORTS + NS_DECL_NSIDOMCAMERAMANAGER + + static already_AddRefed Create(PRUint64 aWindowId); + + void OnNavigation(PRUint64 aWindowId); + +private: + nsDOMCameraManager(); + nsDOMCameraManager(PRUint64 aWindowId); + nsDOMCameraManager(const nsDOMCameraManager&) MOZ_DELETE; + nsDOMCameraManager& operator=(const nsDOMCameraManager&) MOZ_DELETE; + ~nsDOMCameraManager(); + +protected: + PRUint64 mWindowId; + nsCOMPtr mCameraThread; +}; + + +class GetCameraTask : public nsRunnable +{ +public: + GetCameraTask(PRUint32 aCameraId, nsICameraGetCameraCallback* onSuccess, nsICameraErrorCallback* onError, nsIThread* aCameraThread) + : mCameraId(aCameraId) + , mOnSuccessCb(onSuccess) + , mOnErrorCb(onError) + , mCameraThread(aCameraThread) + { } + + NS_IMETHOD Run(); + +protected: + PRUint32 mCameraId; + nsCOMPtr mOnSuccessCb; + nsCOMPtr mOnErrorCb; + nsCOMPtr mCameraThread; +}; + +class GetCameraResult : public nsRunnable +{ +public: + GetCameraResult(nsICameraControl* aCameraControl, nsICameraGetCameraCallback* onSuccess) + : mCameraControl(aCameraControl) + , mOnSuccessCb(onSuccess) + { } + + NS_IMETHOD Run() + { + MOZ_ASSERT(NS_IsMainThread()); + + // TODO: window management stuff + if (mOnSuccessCb) { + mOnSuccessCb->HandleEvent(mCameraControl); + } + return NS_OK; + } + +protected: + nsCOMPtr mCameraControl; + nsCOMPtr mOnSuccessCb; +}; + +#endif // DOM_CAMERA_DOMCAMERAMANAGER_H diff --git a/dom/camera/FallbackCameraCapabilities.cpp b/dom/camera/FallbackCameraCapabilities.cpp new file mode 100644 index 00000000000..c638a976bc6 --- /dev/null +++ b/dom/camera/FallbackCameraCapabilities.cpp @@ -0,0 +1,140 @@ +/* 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 "nsDOMClassInfoID.h" +#include "CameraControl.h" +#include "CameraCapabilities.h" + +#define DOM_CAMERA_LOG_LEVEL 3 +#include "CameraCommon.h" + +using namespace mozilla; + +DOMCI_DATA(CameraCapabilities, nsICameraCapabilities) + +NS_INTERFACE_MAP_BEGIN(nsCameraCapabilities) + NS_INTERFACE_MAP_ENTRY(nsISupports) + NS_INTERFACE_MAP_ENTRY(nsICameraCapabilities) + NS_DOM_INTERFACE_MAP_ENTRY_CLASSINFO(CameraCapabilities) +NS_INTERFACE_MAP_END + +NS_IMPL_ADDREF(nsCameraCapabilities) +NS_IMPL_RELEASE(nsCameraCapabilities) + +nsCameraCapabilities::nsCameraCapabilities(nsCameraControl* aCamera) + : mCamera(aCamera) +{ + /* member initializers and constructor code */ + DOM_CAMERA_LOGI("%s:%d : FALLBACK CAMERA CAPABILITIES\n", __func__, __LINE__); +} + +nsCameraCapabilities::~nsCameraCapabilities() +{ + /* destructor code */ +} + +/* [implicit_jscontext] readonly attribute jsval previewSizes; */ +NS_IMETHODIMP +nsCameraCapabilities::GetPreviewSizes(JSContext* cx, JS::Value* aPreviewSizes) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + +/* [implicit_jscontext] readonly attribute jsval pictureSizes; */ +NS_IMETHODIMP +nsCameraCapabilities::GetPictureSizes(JSContext* cx, JS::Value* aPictureSizes) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + +/* [implicit_jscontext] readonly attribute jsval fileFormats; */ +NS_IMETHODIMP +nsCameraCapabilities::GetFileFormats(JSContext* cx, JS::Value* aFileFormats) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + +/* [implicit_jscontext] readonly attribute jsval whiteBalanceModes; */ +NS_IMETHODIMP +nsCameraCapabilities::GetWhiteBalanceModes(JSContext* cx, JS::Value* aWhiteBalanceModes) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + +/* [implicit_jscontext] readonly attribute jsval sceneModes; */ +NS_IMETHODIMP +nsCameraCapabilities::GetSceneModes(JSContext* cx, JS::Value* aSceneModes) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + +/* [implicit_jscontext] readonly attribute jsval effects; */ +NS_IMETHODIMP +nsCameraCapabilities::GetEffects(JSContext* cx, JS::Value* aEffects) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + +/* [implicit_jscontext] readonly attribute jsval flashModes; */ +NS_IMETHODIMP +nsCameraCapabilities::GetFlashModes(JSContext* cx, JS::Value* aFlashModes) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + +/* [implicit_jscontext] readonly attribute jsval focusModes; */ +NS_IMETHODIMP +nsCameraCapabilities::GetFocusModes(JSContext* cx, JS::Value* aFocusModes) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + +/* [implicit_jscontext] readonly attribute long maxFocusAreas; */ +NS_IMETHODIMP +nsCameraCapabilities::GetMaxFocusAreas(JSContext* cx, PRInt32* aMaxFocusAreas) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + +/* [implicit_jscontext] readonly attribute double minExposureCompensation; */ +NS_IMETHODIMP +nsCameraCapabilities::GetMinExposureCompensation(JSContext* cx, double* aMinExposureCompensation) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + +/* [implicit_jscontext] readonly attribute double maxExposureCompensation; */ +NS_IMETHODIMP +nsCameraCapabilities::GetMaxExposureCompensation(JSContext* cx, double* aMaxExposureCompensation) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + +/* [implicit_jscontext] readonly attribute double stepExposureCompensation; */ +NS_IMETHODIMP +nsCameraCapabilities::GetStepExposureCompensation(JSContext* cx, double* aStepExposureCompensation) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + +/* [implicit_jscontext] readonly attribute long maxMeteringAreas; */ +NS_IMETHODIMP +nsCameraCapabilities::GetMaxMeteringAreas(JSContext* cx, PRInt32* aMaxMeteringAreas) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + +/* [implicit_jscontext] readonly attribute jsval zoomRatios; */ +NS_IMETHODIMP +nsCameraCapabilities::GetZoomRatios(JSContext* cx, JS::Value* aZoomRatios) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + +/* [implicit_jscontext] readonly attribute jsval videoSizes; */ +NS_IMETHODIMP +nsCameraCapabilities::GetVideoSizes(JSContext* cx, JS::Value* aVideoSizes) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} diff --git a/dom/camera/FallbackCameraControl.cpp b/dom/camera/FallbackCameraControl.cpp new file mode 100644 index 00000000000..ca8c016c4ff --- /dev/null +++ b/dom/camera/FallbackCameraControl.cpp @@ -0,0 +1,147 @@ +/* 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 "nsDOMClassInfoID.h" +#include "DOMCameraManager.h" +#include "CameraControl.h" + +using namespace mozilla; + +/** + * Fallback camera control subclass. Can be used as a template for the + * definition of new camera support classes. + */ +class nsFallbackCameraControl : public nsCameraControl +{ +public: + nsFallbackCameraControl(PRUint32 aCameraId, nsIThread* aCameraThread); + + const char* GetParameter(const char* aKey); + const char* GetParameterConstChar(PRUint32 aKey); + double GetParameterDouble(PRUint32 aKey); + void GetParameter(PRUint32 aKey, nsTArray& aRegions); + void SetParameter(const char* aKey, const char* aValue); + void SetParameter(PRUint32 aKey, const char* aValue); + void SetParameter(PRUint32 aKey, double aValue); + void SetParameter(PRUint32 aKey, const nsTArray& aRegions); + void PushParameters(); + +protected: + ~nsFallbackCameraControl(); + + nsresult GetPreviewStreamImpl(GetPreviewStreamTask* aGetPreviewStream); + nsresult AutoFocusImpl(AutoFocusTask* aAutoFocus); + nsresult TakePictureImpl(TakePictureTask* aTakePicture); + nsresult StartRecordingImpl(StartRecordingTask* aStartRecording); + nsresult StopRecordingImpl(StopRecordingTask* aStopRecording); + nsresult PushParametersImpl(PushParametersTask* aPushParameters); + nsresult PullParametersImpl(PullParametersTask* aPullParameters); + +private: + nsFallbackCameraControl(const nsFallbackCameraControl&) MOZ_DELETE; + nsFallbackCameraControl& operator=(const nsFallbackCameraControl&) MOZ_DELETE; +}; + +/** + * Stub implemetations of the fallback camera control. + * + * None of these should ever get called--they exist to keep the linker happy, + * and may be used as templates for new camera support classes. + */ +nsFallbackCameraControl::nsFallbackCameraControl(PRUint32 aCameraId, nsIThread* aCameraThread) + : nsCameraControl(aCameraId, aCameraThread) +{ } + +nsFallbackCameraControl::~nsFallbackCameraControl() +{ } + +const char* +nsFallbackCameraControl::GetParameter(const char* aKey) +{ + return nullptr; +} + +const char* +nsFallbackCameraControl::GetParameterConstChar(PRUint32 aKey) +{ + return nullptr; +} + +double +nsFallbackCameraControl::GetParameterDouble(PRUint32 aKey) +{ + return NAN; +} + +void +nsFallbackCameraControl::GetParameter(PRUint32 aKey, nsTArray& aRegions) +{ +} + +void +nsFallbackCameraControl::SetParameter(const char* aKey, const char* aValue) +{ +} + +void +nsFallbackCameraControl::SetParameter(PRUint32 aKey, const char* aValue) +{ +} + +void +nsFallbackCameraControl::SetParameter(PRUint32 aKey, double aValue) +{ +} + +void +nsFallbackCameraControl::SetParameter(PRUint32 aKey, const nsTArray& aRegions) +{ +} + +void +nsFallbackCameraControl::PushParameters() +{ +} + +nsresult +nsFallbackCameraControl::GetPreviewStreamImpl(GetPreviewStreamTask* aGetPreviewStream) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + +nsresult +nsFallbackCameraControl::AutoFocusImpl(AutoFocusTask* aAutoFocus) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + +nsresult +nsFallbackCameraControl::TakePictureImpl(TakePictureTask* aTakePicture) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + +nsresult +nsFallbackCameraControl::StartRecordingImpl(StartRecordingTask* aStartRecording) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + +nsresult +nsFallbackCameraControl::StopRecordingImpl(StopRecordingTask* aStopRecording) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + +nsresult +nsFallbackCameraControl::PushParametersImpl(PushParametersTask* aPushParameters) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + +nsresult +nsFallbackCameraControl::PullParametersImpl(PullParametersTask* aPullParameters) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} diff --git a/dom/camera/FallbackCameraManager.cpp b/dom/camera/FallbackCameraManager.cpp new file mode 100644 index 00000000000..964f251585a --- /dev/null +++ b/dom/camera/FallbackCameraManager.cpp @@ -0,0 +1,22 @@ +/* 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 "DOMCameraManager.h" + +// From nsDOMCameraManager. + +/* [implicit_jscontext] jsval getListOfCameras (); */ +NS_IMETHODIMP +nsDOMCameraManager::GetListOfCameras(JSContext* cx, JS::Value* _retval) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + +using namespace mozilla; + +NS_IMETHODIMP +GetCameraTask::Run() +{ + return NS_ERROR_NOT_IMPLEMENTED; +} diff --git a/dom/camera/GonkCameraCapabilities.cpp b/dom/camera/GonkCameraCapabilities.cpp new file mode 100644 index 00000000000..b2420ea6d23 --- /dev/null +++ b/dom/camera/GonkCameraCapabilities.cpp @@ -0,0 +1,352 @@ +/* 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 +#include +#include "nsDOMClassInfo.h" +#include "jsapi.h" +#include "camera/CameraParameters.h" +#include "CameraControl.h" +#include "CameraCapabilities.h" + +#define DOM_CAMERA_LOG_LEVEL 3 +#include "CameraCommon.h" + +using namespace android; +using namespace mozilla; + +DOMCI_DATA(CameraCapabilities, nsICameraCapabilities) + +NS_INTERFACE_MAP_BEGIN(nsCameraCapabilities) + NS_INTERFACE_MAP_ENTRY(nsISupports) + NS_INTERFACE_MAP_ENTRY(nsICameraCapabilities) + NS_DOM_INTERFACE_MAP_ENTRY_CLASSINFO(CameraCapabilities) +NS_INTERFACE_MAP_END + +NS_IMPL_ADDREF(nsCameraCapabilities) +NS_IMPL_RELEASE(nsCameraCapabilities) + + +nsCameraCapabilities::nsCameraCapabilities(nsCameraControl* aCamera) + : mCamera(aCamera) +{ + // member initializers and constructor code + DOM_CAMERA_LOGI("%s:%d : this=%p\n", __func__, __LINE__, this); +} + +nsCameraCapabilities::~nsCameraCapabilities() +{ + // destructor code + DOM_CAMERA_LOGI("%s:%d : this=%p, mCamera=%p\n", __func__, __LINE__, this, mCamera.get()); +} + +static nsresult +ParseZoomRatioItemAndAdd(JSContext* aCx, JSObject* aArray, PRUint32 aIndex, const char* aStart, char** aEnd) +{ + if (!*aEnd) { + // make 'aEnd' follow the same semantics as strchr(). + aEnd = nullptr; + } + + double d = strtod(aStart, aEnd); + jsval v; + + d /= 100; + if (!JS_NewNumberValue(aCx, d, &v)) { + return NS_ERROR_FAILURE; + } + if (!JS_SetElement(aCx, aArray, aIndex, &v)) { + return NS_ERROR_FAILURE; + } + + return NS_OK; +} + +static nsresult +ParseStringItemAndAdd(JSContext* aCx, JSObject* aArray, PRUint32 aIndex, const char* aStart, char** aEnd) +{ + JSString* s; + + if (*aEnd) { + s = JS_NewStringCopyN(aCx, aStart, *aEnd - aStart); + } else { + s = JS_NewStringCopyZ(aCx, aStart); + } + if (!s) { + return NS_ERROR_OUT_OF_MEMORY; + } + + jsval v = STRING_TO_JSVAL(s); + if (!JS_SetElement(aCx, aArray, aIndex, &v)) { + return NS_ERROR_FAILURE; + } + + return NS_OK; +} + +static nsresult +ParseDimensionItemAndAdd(JSContext* aCx, JSObject* aArray, PRUint32 aIndex, const char* aStart, char** aEnd) +{ + char* x; + + if (!*aEnd) { + // make 'aEnd' follow the same semantics as strchr(). + aEnd = nullptr; + } + + jsval w = INT_TO_JSVAL(strtol(aStart, &x, 10)); + jsval h = INT_TO_JSVAL(strtol(x + 1, aEnd, 10)); + + JSObject* o = JS_NewObject(aCx, nullptr, nullptr, nullptr); + if (!o) { + return NS_ERROR_OUT_OF_MEMORY; + } + + if (!JS_SetProperty(aCx, o, "width", &w)) { + return NS_ERROR_FAILURE; + } + if (!JS_SetProperty(aCx, o, "height", &h)) { + return NS_ERROR_FAILURE; + } + + jsval v = OBJECT_TO_JSVAL(o); + if (!JS_SetElement(aCx, aArray, aIndex, &v)) { + return NS_ERROR_FAILURE; + } + + return NS_OK; +} + +nsresult +nsCameraCapabilities::ParameterListToNewArray(JSContext* aCx, JSObject** aArray, const char* aKey, ParseItemAndAddFunc aParseItemAndAdd) +{ + NS_ENSURE_TRUE(mCamera, NS_ERROR_NOT_AVAILABLE); + + const char* value = mCamera->GetParameter(aKey); + if (!value) { + // in case we get nonsense data back + *aArray = nullptr; + return NS_OK; + } + + *aArray = JS_NewArrayObject(aCx, 0, nullptr); + if (!*aArray) { + return NS_ERROR_OUT_OF_MEMORY; + } + + const char* p = value; + PRUint32 index = 0; + nsresult rv; + char* q; + + while (p) { + q = strchr(p, ','); + if (q != p) { // skip consecutive delimiters, just in case + rv = aParseItemAndAdd(aCx, *aArray, index, p, &q); + NS_ENSURE_SUCCESS(rv, rv); + ++index; + } + p = q; + if (p) { + ++p; + } + } + + return JS_FreezeObject(aCx, *aArray) ? NS_OK : NS_ERROR_FAILURE; +} + +nsresult +nsCameraCapabilities::StringListToNewObject(JSContext* aCx, JS::Value* aArray, const char* aKey) +{ + JSObject* array; + + nsresult rv = ParameterListToNewArray(aCx, &array, aKey, ParseStringItemAndAdd); + NS_ENSURE_SUCCESS(rv, rv); + + *aArray = OBJECT_TO_JSVAL(array); + return NS_OK; +} + +nsresult +nsCameraCapabilities::DimensionListToNewObject(JSContext* aCx, JS::Value* aArray, const char* aKey) +{ + JSObject* array; + nsresult rv; + + rv = ParameterListToNewArray(aCx, &array, aKey, ParseDimensionItemAndAdd); + NS_ENSURE_SUCCESS(rv, rv); + + *aArray = OBJECT_TO_JSVAL(array); + return NS_OK; +} + +/* readonly attribute jsval previewSizes; */ +NS_IMETHODIMP +nsCameraCapabilities::GetPreviewSizes(JSContext* cx, JS::Value* aPreviewSizes) +{ + return DimensionListToNewObject(cx, aPreviewSizes, CameraParameters::KEY_SUPPORTED_PREVIEW_SIZES); +} + +/* readonly attribute jsval pictureSizes; */ +NS_IMETHODIMP +nsCameraCapabilities::GetPictureSizes(JSContext* cx, JS::Value* aPictureSizes) +{ + return DimensionListToNewObject(cx, aPictureSizes, CameraParameters::KEY_SUPPORTED_PICTURE_SIZES); +} + +/* readonly attribute jsval fileFormats; */ +NS_IMETHODIMP +nsCameraCapabilities::GetFileFormats(JSContext* cx, JS::Value* aFileFormats) +{ + return StringListToNewObject(cx, aFileFormats, CameraParameters::KEY_SUPPORTED_PICTURE_FORMATS); +} + +/* readonly attribute jsval whiteBalanceModes; */ +NS_IMETHODIMP +nsCameraCapabilities::GetWhiteBalanceModes(JSContext* cx, JS::Value* aWhiteBalanceModes) +{ + return StringListToNewObject(cx, aWhiteBalanceModes, CameraParameters::KEY_SUPPORTED_WHITE_BALANCE); +} + +/* readonly attribute jsval sceneModes; */ +NS_IMETHODIMP +nsCameraCapabilities::GetSceneModes(JSContext* cx, JS::Value* aSceneModes) +{ + return StringListToNewObject(cx, aSceneModes, CameraParameters::KEY_SUPPORTED_SCENE_MODES); +} + +/* readonly attribute jsval effects; */ +NS_IMETHODIMP +nsCameraCapabilities::GetEffects(JSContext* cx, JS::Value* aEffects) +{ + return StringListToNewObject(cx, aEffects, CameraParameters::KEY_SUPPORTED_EFFECTS); +} + +/* readonly attribute jsval flashModes; */ +NS_IMETHODIMP +nsCameraCapabilities::GetFlashModes(JSContext* cx, JS::Value* aFlashModes) +{ + return StringListToNewObject(cx, aFlashModes, CameraParameters::KEY_SUPPORTED_FLASH_MODES); +} + +/* readonly attribute jsval focusModes; */ +NS_IMETHODIMP +nsCameraCapabilities::GetFocusModes(JSContext* cx, JS::Value* aFocusModes) +{ + return StringListToNewObject(cx, aFocusModes, CameraParameters::KEY_SUPPORTED_FOCUS_MODES); +} + +/* readonly attribute long maxFocusAreas; */ +NS_IMETHODIMP +nsCameraCapabilities::GetMaxFocusAreas(JSContext* cx, PRInt32* aMaxFocusAreas) +{ + NS_ENSURE_TRUE(mCamera, NS_ERROR_NOT_AVAILABLE); + + const char* value = mCamera->GetParameter(CameraParameters::KEY_MAX_NUM_FOCUS_AREAS); + if (!value) { + // in case we get nonsense data back + *aMaxFocusAreas = 0; + return NS_OK; + } + + *aMaxFocusAreas = atoi(value); + return NS_OK; +} + +/* readonly attribute double minExposureCompensation; */ +NS_IMETHODIMP +nsCameraCapabilities::GetMinExposureCompensation(JSContext* cx, double* aMinExposureCompensation) +{ + NS_ENSURE_TRUE(mCamera, NS_ERROR_NOT_AVAILABLE); + + const char* value = mCamera->GetParameter(CameraParameters::KEY_MIN_EXPOSURE_COMPENSATION); + if (!value) { + // in case we get nonsense data back + *aMinExposureCompensation = 0; + return NS_OK; + } + + *aMinExposureCompensation = atof(value); + return NS_OK; +} + +/* readonly attribute double maxExposureCompensation; */ +NS_IMETHODIMP +nsCameraCapabilities::GetMaxExposureCompensation(JSContext* cx, double* aMaxExposureCompensation) +{ + NS_ENSURE_TRUE(mCamera, NS_ERROR_NOT_AVAILABLE); + + const char* value = mCamera->GetParameter(CameraParameters::KEY_MAX_EXPOSURE_COMPENSATION); + if (!value) { + // in case we get nonsense data back + *aMaxExposureCompensation = 0; + return NS_OK; + } + + *aMaxExposureCompensation = atof(value); + return NS_OK; +} + +/* readonly attribute double stepExposureCompensation; */ +NS_IMETHODIMP +nsCameraCapabilities::GetStepExposureCompensation(JSContext* cx, double* aStepExposureCompensation) +{ + NS_ENSURE_TRUE(mCamera, NS_ERROR_NOT_AVAILABLE); + + const char* value = mCamera->GetParameter(CameraParameters::KEY_EXPOSURE_COMPENSATION_STEP); + if (!value) { + // in case we get nonsense data back + *aStepExposureCompensation = 0; + return NS_OK; + } + + *aStepExposureCompensation = atof(value); + return NS_OK; +} + +/* readonly attribute long maxMeteringAreas; */ +NS_IMETHODIMP +nsCameraCapabilities::GetMaxMeteringAreas(JSContext* cx, PRInt32* aMaxMeteringAreas) +{ + NS_ENSURE_TRUE(mCamera, NS_ERROR_NOT_AVAILABLE); + + const char* value = mCamera->GetParameter(CameraParameters::KEY_MAX_NUM_METERING_AREAS); + if (!value) { + // in case we get nonsense data back + *aMaxMeteringAreas = 0; + return NS_OK; + } + + *aMaxMeteringAreas = atoi(value); + return NS_OK; +} + +/* readonly attribute jsval zoomRatios; */ +NS_IMETHODIMP +nsCameraCapabilities::GetZoomRatios(JSContext* cx, JS::Value* aZoomRatios) +{ + NS_ENSURE_TRUE(mCamera, NS_ERROR_NOT_AVAILABLE); + + const char* value = mCamera->GetParameter(CameraParameters::KEY_ZOOM_SUPPORTED); + if (!value || strcmp(value, CameraParameters::TRUE) != 0) { + // if zoom is not supported, return a null object + *aZoomRatios = JSVAL_NULL; + return NS_OK; + } + + JSObject* array; + + nsresult rv = ParameterListToNewArray(cx, &array, CameraParameters::KEY_ZOOM_RATIOS, ParseZoomRatioItemAndAdd); + NS_ENSURE_SUCCESS(rv, rv); + + *aZoomRatios = OBJECT_TO_JSVAL(array); + return NS_OK; +} + +/* readonly attribute jsval videoSizes; */ +NS_IMETHODIMP +nsCameraCapabilities::GetVideoSizes(JSContext* cx, JS::Value* aVideoSizes) +{ + return DimensionListToNewObject(cx, aVideoSizes, CameraParameters::KEY_SUPPORTED_VIDEO_SIZES); +} diff --git a/dom/camera/GonkCameraControl.cpp b/dom/camera/GonkCameraControl.cpp new file mode 100644 index 00000000000..06e8c594dc2 --- /dev/null +++ b/dom/camera/GonkCameraControl.cpp @@ -0,0 +1,602 @@ +/* + * Copyright (C) 2012 Mozilla Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include "libcameraservice/CameraHardwareInterface.h" +#include "camera/CameraParameters.h" +#include "nsCOMPtr.h" +#include "nsDOMClassInfo.h" +#include "nsMemory.h" +#include "jsapi.h" +#include "nsThread.h" +#include "nsPrintfCString.h" +#include "DOMCameraManager.h" +#include "GonkCameraHwMgr.h" +#include "CameraCapabilities.h" +#include "GonkCameraControl.h" +#include "GonkCameraPreview.h" + +#define DOM_CAMERA_LOG_LEVEL 3 +#include "CameraCommon.h" + +using namespace mozilla; + +static const char* getKeyText(PRUint32 aKey) +{ + switch (aKey) { + case nsCameraControl::CAMERA_PARAM_EFFECT: + return CameraParameters::KEY_EFFECT; + case nsCameraControl::CAMERA_PARAM_WHITEBALANCE: + return CameraParameters::KEY_WHITE_BALANCE; + case nsCameraControl::CAMERA_PARAM_SCENEMODE: + return CameraParameters::KEY_SCENE_MODE; + case nsCameraControl::CAMERA_PARAM_FLASHMODE: + return CameraParameters::KEY_FLASH_MODE; + case nsCameraControl::CAMERA_PARAM_FOCUSMODE: + return CameraParameters::KEY_FOCUS_MODE; + case nsCameraControl::CAMERA_PARAM_ZOOM: + return CameraParameters::KEY_ZOOM; + case nsCameraControl::CAMERA_PARAM_METERINGAREAS: + return CameraParameters::KEY_METERING_AREAS; + case nsCameraControl::CAMERA_PARAM_FOCUSAREAS: + return CameraParameters::KEY_FOCUS_AREAS; + case nsCameraControl::CAMERA_PARAM_FOCALLENGTH: + return CameraParameters::KEY_FOCAL_LENGTH; + case nsCameraControl::CAMERA_PARAM_FOCUSDISTANCENEAR: + return CameraParameters::KEY_FOCUS_DISTANCES; + case nsCameraControl::CAMERA_PARAM_FOCUSDISTANCEOPTIMUM: + return CameraParameters::KEY_FOCUS_DISTANCES; + case nsCameraControl::CAMERA_PARAM_FOCUSDISTANCEFAR: + return CameraParameters::KEY_FOCUS_DISTANCES; + case nsCameraControl::CAMERA_PARAM_EXPOSURECOMPENSATION: + return CameraParameters::KEY_EXPOSURE_COMPENSATION; + case nsCameraControl::CAMERA_PARAM_SUPPORTED_PREVIEWSIZES: + return CameraParameters::KEY_SUPPORTED_PREVIEW_SIZES; + case nsCameraControl::CAMERA_PARAM_SUPPORTED_VIDEOSIZES: + return CameraParameters::KEY_SUPPORTED_VIDEO_SIZES; + case nsCameraControl::CAMERA_PARAM_SUPPORTED_PICTURESIZES: + return CameraParameters::KEY_SUPPORTED_PICTURE_SIZES; + case nsCameraControl::CAMERA_PARAM_SUPPORTED_PICTUREFORMATS: + return CameraParameters::KEY_SUPPORTED_PICTURE_FORMATS; + case nsCameraControl::CAMERA_PARAM_SUPPORTED_WHITEBALANCES: + return CameraParameters::KEY_SUPPORTED_WHITE_BALANCE; + case nsCameraControl::CAMERA_PARAM_SUPPORTED_SCENEMODES: + return CameraParameters::KEY_SUPPORTED_SCENE_MODES; + case nsCameraControl::CAMERA_PARAM_SUPPORTED_EFFECTS: + return CameraParameters::KEY_SUPPORTED_EFFECTS; + case nsCameraControl::CAMERA_PARAM_SUPPORTED_FLASHMODES: + return CameraParameters::KEY_SUPPORTED_FLASH_MODES; + case nsCameraControl::CAMERA_PARAM_SUPPORTED_FOCUSMODES: + return CameraParameters::KEY_SUPPORTED_FOCUS_MODES; + case nsCameraControl::CAMERA_PARAM_SUPPORTED_MAXFOCUSAREAS: + return CameraParameters::KEY_MAX_NUM_FOCUS_AREAS; + case nsCameraControl::CAMERA_PARAM_SUPPORTED_MAXMETERINGAREAS: + return CameraParameters::KEY_MAX_NUM_METERING_AREAS; + case nsCameraControl::CAMERA_PARAM_SUPPORTED_MINEXPOSURECOMPENSATION: + return CameraParameters::KEY_MIN_EXPOSURE_COMPENSATION; + case nsCameraControl::CAMERA_PARAM_SUPPORTED_MAXEXPOSURECOMPENSATION: + return CameraParameters::KEY_MAX_EXPOSURE_COMPENSATION; + case nsCameraControl::CAMERA_PARAM_SUPPORTED_EXPOSURECOMPENSATIONSTEP: + return CameraParameters::KEY_EXPOSURE_COMPENSATION_STEP; + case nsCameraControl::CAMERA_PARAM_SUPPORTED_ZOOM: + return CameraParameters::KEY_ZOOM_SUPPORTED; + case nsCameraControl::CAMERA_PARAM_SUPPORTED_ZOOMRATIOS: + return CameraParameters::KEY_ZOOM_RATIOS; + default: + return nullptr; + } +} + +// Gonk-specific CameraControl implementation. + +nsGonkCameraControl::nsGonkCameraControl(PRUint32 aCameraId, nsIThread* aCameraThread) + : nsCameraControl(aCameraId, aCameraThread) + , mHwHandle(0) + , mExposureCompensationMin(0.0) + , mExposureCompensationStep(0.0) + , mDeferConfigUpdate(false) +{ + // Constructor runs on the camera thread--see DOMCameraManager.cpp::GetCameraImpl(). + DOM_CAMERA_LOGI("%s:%d\n", __func__, __LINE__); + mHwHandle = GonkCameraHardware::GetHandle(this, mCameraId); + DOM_CAMERA_LOGI("%s:%d : this = %p, mHwHandle = %d\n", __func__, __LINE__, this, mHwHandle); + + // Initialize our camera configuration database. + mRwLock = PR_NewRWLock(PR_RWLOCK_RANK_NONE, "GonkCameraControl.Parameters.Lock"); + PullParametersImpl(nullptr); + + // Grab any settings we'll need later. + mExposureCompensationMin = mParams.getFloat(CameraParameters::KEY_MIN_EXPOSURE_COMPENSATION); + mExposureCompensationStep = mParams.getFloat(CameraParameters::KEY_EXPOSURE_COMPENSATION_STEP); + mMaxMeteringAreas = mParams.getInt(CameraParameters::KEY_MAX_NUM_METERING_AREAS); + mMaxFocusAreas = mParams.getInt(CameraParameters::KEY_MAX_NUM_FOCUS_AREAS); + + DOM_CAMERA_LOGI("minimum exposure compensation = %f\n", mExposureCompensationMin); + DOM_CAMERA_LOGI("exposure compensation step = %f\n", mExposureCompensationStep); + DOM_CAMERA_LOGI("maximum metering areas = %d\n", mMaxMeteringAreas); + DOM_CAMERA_LOGI("maximum focus areas = %d\n", mMaxFocusAreas); +} + +nsGonkCameraControl::~nsGonkCameraControl() +{ + DOM_CAMERA_LOGI("%s:%d : this = %p, mHwHandle = %d\n", __func__, __LINE__, this, mHwHandle); + GonkCameraHardware::ReleaseHandle(mHwHandle); + if (mRwLock) { + PRRWLock* lock = mRwLock; + mRwLock = nullptr; + PR_DestroyRWLock(lock); + } + + DOM_CAMERA_LOGI("%s:%d\n", __func__, __LINE__); +} + +class RwAutoLockRead +{ +public: + RwAutoLockRead(PRRWLock* aRwLock) + : mRwLock(aRwLock) + { + PR_RWLock_Rlock(mRwLock); + } + + ~RwAutoLockRead() + { + PR_RWLock_Unlock(mRwLock); + } + +protected: + PRRWLock* mRwLock; +}; + +class RwAutoLockWrite +{ +public: + RwAutoLockWrite(PRRWLock* aRwLock) + : mRwLock(aRwLock) + { + PR_RWLock_Wlock(mRwLock); + } + + ~RwAutoLockWrite() + { + PR_RWLock_Unlock(mRwLock); + } + +protected: + PRRWLock* mRwLock; +}; + +const char* +nsGonkCameraControl::GetParameter(const char* aKey) +{ + RwAutoLockRead lock(mRwLock); + return mParams.get(aKey); +} + +const char* +nsGonkCameraControl::GetParameterConstChar(PRUint32 aKey) +{ + const char* key = getKeyText(aKey); + if (!key) { + return nullptr; + } + + RwAutoLockRead lock(mRwLock); + return mParams.get(key); +} + +double +nsGonkCameraControl::GetParameterDouble(PRUint32 aKey) +{ + double val; + int index = 0; + double focusDistance[3]; + const char* s; + + const char* key = getKeyText(aKey); + if (!key) { + // return 1x when zooming is not supported + return aKey == CAMERA_PARAM_ZOOM ? 1.0 : 0.0; + } + + RwAutoLockRead lock(mRwLock); + switch (aKey) { + case CAMERA_PARAM_ZOOM: + val = mParams.getInt(key); + return val / 100; + + /** + * The gonk camera parameters API only exposes one focus distance property + * that contains "Near,Optimum,Far" distances, in metres, where 'Far' may + * be 'Infinity'. + */ + case CAMERA_PARAM_FOCUSDISTANCEFAR: + ++index; + // intentional fallthrough + + case CAMERA_PARAM_FOCUSDISTANCEOPTIMUM: + ++index; + // intentional fallthrough + + case CAMERA_PARAM_FOCUSDISTANCENEAR: + s = mParams.get(key); + if (sscanf(s, "%lf,%lf,%lf", &focusDistance[0], &focusDistance[1], &focusDistance[2]) == 3) { + return focusDistance[index]; + } + return 0.0; + + case CAMERA_PARAM_EXPOSURECOMPENSATION: + index = mParams.getInt(key); + if (!index) { + // NaN indicates automatic exposure compensation + return NAN; + } + val = (index - 1) * mExposureCompensationStep + mExposureCompensationMin; + DOM_CAMERA_LOGI("index = %d --> compensation = %f\n", index, val); + return val; + + default: + return mParams.getFloat(key); + } +} + +void +nsGonkCameraControl::GetParameter(PRUint32 aKey, nsTArray& aRegions) +{ + aRegions.Clear(); + + const char* key = getKeyText(aKey); + if (!key) { + return; + } + + RwAutoLockRead lock(mRwLock); + + const char* value = mParams.get(key); + DOM_CAMERA_LOGI("key='%s' --> value='%s'\n", key, value); + if (!value) { + return; + } + + const char* p = value; + PRUint32 count = 1; + + // count the number of regions in the string + while ((p = strstr(p, "),("))) { + ++count; + p += 3; + } + + aRegions.SetCapacity(count); + CameraRegion* r; + + // parse all of the region sets + PRUint32 i; + for (i = 0, p = value; p && i < count; ++i, p = strchr(p + 1, '(')) { + r = aRegions.AppendElement(); + if (sscanf(p, "(%d,%d,%d,%d,%u)", &r->top, &r->left, &r->bottom, &r->right, &r->weight) != 5) { + DOM_CAMERA_LOGE("%s:%d : region tuple has bad format: '%s'\n", __func__, __LINE__, p); + goto GetParameter_error; + } + } + + return; + +GetParameter_error: + aRegions.Clear(); +} + +void +nsGonkCameraControl::PushParameters() +{ + if (!mDeferConfigUpdate) { + DOM_CAMERA_LOGI("%s:%d\n", __func__, __LINE__); + /** + * If we're already on the camera thread, call PushParametersImpl() + * directly, so that it executes synchronously. Some callers + * require this so that changes take effect immediately before + * we can proceed. + */ + if (NS_IsMainThread()) { + nsCOMPtr pushParametersTask = new PushParametersTask(this); + mCameraThread->Dispatch(pushParametersTask, NS_DISPATCH_NORMAL); + } else { + PushParametersImpl(nullptr); + } + } +} + +void +nsGonkCameraControl::SetParameter(const char* aKey, const char* aValue) +{ + { + RwAutoLockWrite lock(mRwLock); + mParams.set(aKey, aValue); + } + PushParameters(); +} + +void +nsGonkCameraControl::SetParameter(PRUint32 aKey, const char* aValue) +{ + const char* key = getKeyText(aKey); + if (!key) { + return; + } + + { + RwAutoLockWrite lock(mRwLock); + mParams.set(key, aValue); + } + PushParameters(); +} + +void +nsGonkCameraControl::SetParameter(PRUint32 aKey, double aValue) +{ + PRUint32 index; + + const char* key = getKeyText(aKey); + if (!key) { + return; + } + + { + RwAutoLockWrite lock(mRwLock); + if (aKey == CAMERA_PARAM_EXPOSURECOMPENSATION) { + /** + * Convert from real value to a Gonk index, round + * to the nearest step; index is 1-based. + */ + index = (aValue - mExposureCompensationMin + mExposureCompensationStep / 2) / mExposureCompensationStep + 1; + DOM_CAMERA_LOGI("compensation = %f --> index = %d\n", aValue, index); + mParams.set(key, index); + } else { + mParams.setFloat(key, aValue); + } + } + PushParameters(); +} + +void +nsGonkCameraControl::SetParameter(PRUint32 aKey, const nsTArray& aRegions) +{ + const char* key = getKeyText(aKey); + if (!key) { + return; + } + + PRUint32 length = aRegions.Length(); + + if (!length) { + // This tells the camera driver to revert to automatic regioning. + mParams.set(key, "(0,0,0,0,0)"); + PushParameters(); + return; + } + + nsCString s; + + for (PRUint32 i = 0; i < length; ++i) { + const CameraRegion* r = &aRegions[i]; + s.AppendPrintf("(%d,%d,%d,%d,%d),", r->top, r->left, r->bottom, r->right, r->weight); + } + + // remove the trailing comma + s.Trim(",", false, true, true); + + DOM_CAMERA_LOGI("camera region string '%s'\n", s.get()); + + { + RwAutoLockWrite lock(mRwLock); + mParams.set(key, s.get()); + } + PushParameters(); +} + +nsresult +nsGonkCameraControl::GetPreviewStreamImpl(GetPreviewStreamTask* aGetPreviewStream) +{ + nsCOMPtr preview = mPreview; + nsresult rv; + + if (!preview) { + preview = new GonkCameraPreview(mHwHandle, aGetPreviewStream->mSize.width, aGetPreviewStream->mSize.height); + if (!preview) { + if (aGetPreviewStream->mOnErrorCb) { + rv = NS_DispatchToMainThread(new CameraErrorResult(aGetPreviewStream->mOnErrorCb, NS_LITERAL_STRING("OUT_OF_MEMORY"))); + NS_ENSURE_SUCCESS(rv, rv); + } + return NS_ERROR_OUT_OF_MEMORY; + } + } + + mPreview = preview; + return NS_DispatchToMainThread(new GetPreviewStreamResult(preview.get(), aGetPreviewStream->mOnSuccessCb)); +} + +nsresult +nsGonkCameraControl::AutoFocusImpl(AutoFocusTask* aAutoFocus) +{ + nsCOMPtr cb = mAutoFocusOnSuccessCb; + if (cb) { + /** + * We already have a callback, so someone has already + * called autoFocus() -- cancel it. + */ + mAutoFocusOnSuccessCb = nullptr; + nsCOMPtr ecb = mAutoFocusOnErrorCb; + mAutoFocusOnErrorCb = nullptr; + if (ecb) { + nsresult rv = NS_DispatchToMainThread(new CameraErrorResult(ecb, NS_LITERAL_STRING("CANCELLED"))); + NS_ENSURE_SUCCESS(rv, rv); + } + + GonkCameraHardware::CancelAutoFocus(mHwHandle); + } + + mAutoFocusOnSuccessCb = aAutoFocus->mOnSuccessCb; + mAutoFocusOnErrorCb = aAutoFocus->mOnErrorCb; + + if (GonkCameraHardware::AutoFocus(mHwHandle) != OK) { + return NS_ERROR_FAILURE; + } + return NS_OK; +} + +nsresult +nsGonkCameraControl::TakePictureImpl(TakePictureTask* aTakePicture) +{ + nsCOMPtr cb = mTakePictureOnSuccessCb; + if (cb) { + /** + * We already have a callback, so someone has already + * called TakePicture() -- cancel it. + */ + mTakePictureOnSuccessCb = nullptr; + nsCOMPtr ecb = mTakePictureOnErrorCb; + mTakePictureOnErrorCb = nullptr; + if (ecb) { + nsresult rv = NS_DispatchToMainThread(new CameraErrorResult(ecb, NS_LITERAL_STRING("CANCELLED"))); + NS_ENSURE_SUCCESS(rv, rv); + } + + GonkCameraHardware::CancelTakePicture(mHwHandle); + } + + mTakePictureOnSuccessCb = aTakePicture->mOnSuccessCb; + mTakePictureOnErrorCb = aTakePicture->mOnErrorCb; + + // batch-update camera configuration + mDeferConfigUpdate = true; + + /** + * height and width: some drivers are less friendly about getting one of + * these set to zero, so if either is not specified, ignore both and go + * with current or default settings. + */ + if (aTakePicture->mSize.width && aTakePicture->mSize.height) { + nsCString s; + s.AppendPrintf("%dx%d", aTakePicture->mSize.width, aTakePicture->mSize.height); + DOM_CAMERA_LOGI("setting picture size to '%s'\n", s.get()); + SetParameter(CameraParameters::KEY_PICTURE_SIZE, s.get()); + } + + // Picture format -- need to keep it for the callback. + mFileFormat = aTakePicture->mFileFormat; + SetParameter(CameraParameters::KEY_PICTURE_FORMAT, NS_ConvertUTF16toUTF8(mFileFormat).get()); + + // Convert 'rotation' to a positive value from 0..270 degrees, in steps of 90. + PRUint32 r = static_cast(aTakePicture->mRotation); + r %= 360; + r += 45; + r /= 90; + r *= 90; + DOM_CAMERA_LOGI("setting picture rotation to %d degrees (mapped from %d)\n", r, aTakePicture->mRotation); + SetParameter(CameraParameters::KEY_ROTATION, nsPrintfCString("%u", r).get()); + + // Add any specified positional information -- don't care if these fail. + if (!isnan(aTakePicture->mPosition.latitude)) { + DOM_CAMERA_LOGI("setting picture latitude to %lf\n", aTakePicture->mPosition.latitude); + SetParameter(CameraParameters::KEY_GPS_LATITUDE, nsPrintfCString("%lf", aTakePicture->mPosition.latitude).get()); + } + if (!isnan(aTakePicture->mPosition.longitude)) { + DOM_CAMERA_LOGI("setting picture longitude to %lf\n", aTakePicture->mPosition.longitude); + SetParameter(CameraParameters::KEY_GPS_LONGITUDE, nsPrintfCString("%lf", aTakePicture->mPosition.longitude).get()); + } + if (!isnan(aTakePicture->mPosition.altitude)) { + DOM_CAMERA_LOGI("setting picture altitude to %lf\n", aTakePicture->mPosition.altitude); + SetParameter(CameraParameters::KEY_GPS_ALTITUDE, nsPrintfCString("%lf", aTakePicture->mPosition.altitude).get()); + } + if (!isnan(aTakePicture->mPosition.timestamp)) { + DOM_CAMERA_LOGI("setting picture timestamp to %lf\n", aTakePicture->mPosition.timestamp); + SetParameter(CameraParameters::KEY_GPS_TIMESTAMP, nsPrintfCString("%lf", aTakePicture->mPosition.timestamp).get()); + } + + mDeferConfigUpdate = false; + PushParameters(); + + if (GonkCameraHardware::TakePicture(mHwHandle) != OK) { + return NS_ERROR_FAILURE; + } + return NS_OK; +} + +nsresult +nsGonkCameraControl::PushParametersImpl(PushParametersTask* aPushParameters) +{ + RwAutoLockRead lock(mRwLock); + if (GonkCameraHardware::PushParameters(mHwHandle, mParams) != OK) { + return NS_ERROR_FAILURE; + } + + return NS_OK; +} + +nsresult +nsGonkCameraControl::PullParametersImpl(PullParametersTask* aPullParameters) +{ + RwAutoLockWrite lock(mRwLock); + GonkCameraHardware::PullParameters(mHwHandle, mParams); + return NS_OK; +} + +nsresult +nsGonkCameraControl::StartRecordingImpl(StartRecordingTask* aStartRecording) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + +nsresult +nsGonkCameraControl::StopRecordingImpl(StopRecordingTask* aStopRecording) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + +void +nsGonkCameraControl::ReceiveFrame(PRUint8* aData, PRUint32 aLength) +{ + nsCOMPtr preview = mPreview; + + if (preview) { + GonkCameraPreview* p = static_cast(preview.get()); + MOZ_ASSERT(p); + p->ReceiveFrame(aData, aLength); + } +} + +// Gonk callback handlers. +namespace mozilla { + +void +ReceiveImage(nsGonkCameraControl* gc, PRUint8* aData, PRUint32 aLength) +{ + gc->TakePictureComplete(aData, aLength); +} + +void +AutoFocusComplete(nsGonkCameraControl* gc, bool success) +{ + gc->AutoFocusComplete(success); +} + +void +ReceiveFrame(nsGonkCameraControl* gc, PRUint8* aData, PRUint32 aLength) +{ + gc->ReceiveFrame(aData, aLength); +} + +} // namespace mozilla diff --git a/dom/camera/GonkCameraControl.h b/dom/camera/GonkCameraControl.h new file mode 100644 index 00000000000..a579de0de88 --- /dev/null +++ b/dom/camera/GonkCameraControl.h @@ -0,0 +1,76 @@ +/* + * Copyright (C) 2012 Mozilla Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef DOM_CAMERA_GONKCAMERACONTROL_H +#define DOM_CAMERA_GONKCAMERACONTROL_H + +#include "prtypes.h" +#include "prrwlock.h" +#include "CameraControl.h" + +#define DOM_CAMERA_LOG_LEVEL 3 +#include "CameraCommon.h" + +namespace mozilla { + +class nsGonkCameraControl : public nsCameraControl +{ +public: + nsGonkCameraControl(PRUint32 aCameraId, nsIThread* aCameraThread); + + const char* GetParameter(const char* aKey); + const char* GetParameterConstChar(PRUint32 aKey); + double GetParameterDouble(PRUint32 aKey); + void GetParameter(PRUint32 aKey, nsTArray& aRegions); + void SetParameter(const char* aKey, const char* aValue); + void SetParameter(PRUint32 aKey, const char* aValue); + void SetParameter(PRUint32 aKey, double aValue); + void SetParameter(PRUint32 aKey, const nsTArray& aRegions); + void PushParameters(); + + void ReceiveFrame(PRUint8 *aData, PRUint32 aLength); + +protected: + ~nsGonkCameraControl(); + + nsresult GetPreviewStreamImpl(GetPreviewStreamTask* aGetPreviewStream); + nsresult AutoFocusImpl(AutoFocusTask* aAutoFocus); + nsresult TakePictureImpl(TakePictureTask* aTakePicture); + nsresult StartRecordingImpl(StartRecordingTask* aStartRecording); + nsresult StopRecordingImpl(StopRecordingTask* aStopRecording); + nsresult PushParametersImpl(PushParametersTask* aPushParameters); + nsresult PullParametersImpl(PullParametersTask* aPullParameters); + + PRUint32 mHwHandle; + double mExposureCompensationMin; + double mExposureCompensationStep; + bool mDeferConfigUpdate; + PRRWLock* mRwLock; + android::CameraParameters mParams; + +private: + nsGonkCameraControl(const nsGonkCameraControl&) MOZ_DELETE; + nsGonkCameraControl& operator=(const nsGonkCameraControl&) MOZ_DELETE; +}; + +// camera driver callbacks +void ReceiveImage(nsGonkCameraControl* gc, PRUint8* aData, PRUint32 aLength); +void AutoFocusComplete(nsGonkCameraControl* gc, bool success); +void ReceiveFrame(nsGonkCameraControl* gc, PRUint8* aData, PRUint32 aLength); + +} // namespace mozilla + +#endif // DOM_CAMERA_GONKCAMERACONTROL_H diff --git a/dom/camera/GonkCameraHwMgr.cpp b/dom/camera/GonkCameraHwMgr.cpp new file mode 100644 index 00000000000..83313a61801 --- /dev/null +++ b/dom/camera/GonkCameraHwMgr.cpp @@ -0,0 +1,435 @@ +/* + * Copyright (C) 2012 Mozilla Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "nsDebug.h" +#include "GonkCameraHwMgr.h" +#include "GonkNativeWindow.h" + +#define DOM_CAMERA_LOG_LEVEL 3 +#include "CameraCommon.h" + +using namespace mozilla; + +#if GIHM_TIMING_RECEIVEFRAME +#define INCLUDE_TIME_H 1 +#endif +#if GIHM_TIMING_OVERALL +#define INCLUDE_TIME_H 1 +#endif + +#if INCLUDE_TIME_H +#include + +static __inline void timespecSubtract(struct timespec* a, struct timespec* b) +{ + // a = b - a + if (b->tv_nsec < a->tv_nsec) { + b->tv_nsec += 1000000000; + b->tv_sec -= 1; + } + a->tv_nsec = b->tv_nsec - a->tv_nsec; + a->tv_sec = b->tv_sec - a->tv_sec; +} +#endif + +GonkCameraHardware::GonkCameraHardware(GonkCamera* aTarget, PRUint32 aCamera) + : mCamera(aCamera) + , mFps(30) + , mPreviewFormat(PREVIEW_FORMAT_UNKNOWN) + , mClosing(false) + , mMonitor("GonkCameraHardware.Monitor") + , mNumFrames(0) + , mTarget(aTarget) + , mInitialized(false) +{ + DOM_CAMERA_LOGI( "%s: this = %p (aTarget = %p)\n", __func__, (void* )this, (void* )aTarget ); + init(); +} + +// Android data callback +void +GonkCameraHardware::DataCallback(int32_t aMsgType, const sp &aDataPtr, camera_frame_metadata_t* aMetadata, void* aUser) +{ + GonkCameraHardware* hw = GetHardware((PRUint32)aUser); + if (!hw) { + DOM_CAMERA_LOGW("%s:aUser = %d resolved to no camera hw\n", __func__, (PRUint32)aUser); + return; + } + if (hw->mClosing) { + return; + } + + GonkCamera* camera = hw->mTarget; + if (camera) { + switch (aMsgType) { + case CAMERA_MSG_PREVIEW_FRAME: + ReceiveFrame(camera, (PRUint8*)aDataPtr->pointer(), aDataPtr->size()); + break; + + case CAMERA_MSG_COMPRESSED_IMAGE: + ReceiveImage(camera, (PRUint8*)aDataPtr->pointer(), aDataPtr->size()); + break; + + default: + DOM_CAMERA_LOGE("Unhandled data callback event %d\n", aMsgType); + break; + } + } else { + DOM_CAMERA_LOGW("%s: hw = %p (camera = NULL)\n", __func__, hw); + } +} + +// Android notify callback +void +GonkCameraHardware::NotifyCallback(int32_t aMsgType, int32_t ext1, int32_t ext2, void* aUser) +{ + bool bSuccess; + GonkCameraHardware* hw = GetHardware((PRUint32)aUser); + if (!hw) { + DOM_CAMERA_LOGW("%s:aUser = %d resolved to no camera hw\n", __func__, (PRUint32)aUser); + return; + } + if (hw->mClosing) { + return; + } + + GonkCamera* camera = hw->mTarget; + if (!camera) { + return; + } + + switch (aMsgType) { + case CAMERA_MSG_FOCUS: + if (ext1) { + DOM_CAMERA_LOGI("Autofocus complete"); + bSuccess = true; + } else { + DOM_CAMERA_LOGW("Autofocus failed"); + bSuccess = false; + } + AutoFocusComplete(camera, bSuccess); + break; + + case CAMERA_MSG_SHUTTER: + DOM_CAMERA_LOGW("Shutter event not handled yet\n"); + break; + + default: + DOM_CAMERA_LOGE("Unhandled notify callback event %d\n", aMsgType); + break; + } +} + +void +GonkCameraHardware::init() +{ + DOM_CAMERA_LOGI("%s: this = %p\n", __func__, (void* )this); + + if (hw_get_module(CAMERA_HARDWARE_MODULE_ID, (const hw_module_t**)&mModule) < 0) { + return; + } + char cameraDeviceName[4]; + snprintf(cameraDeviceName, sizeof(cameraDeviceName), "%d", mCamera); + mHardware = new CameraHardwareInterface(cameraDeviceName); + if (mHardware->initialize(&mModule->common) != OK) { + mHardware.clear(); + return; + } + + mWindow = new android::GonkNativeWindow(); + + if (sHwHandle == 0) { + sHwHandle = 1; // don't use 0 + } + mHardware->setCallbacks(GonkCameraHardware::NotifyCallback, GonkCameraHardware::DataCallback, NULL, (void*)sHwHandle); + + // initialize the local camera parameter database + mParams = mHardware->getParameters(); + + mHardware->setPreviewWindow(mWindow); + + mInitialized = true; +} + +GonkCameraHardware::~GonkCameraHardware() +{ + DOM_CAMERA_LOGI( "%s:%d : this = %p\n", __func__, __LINE__, (void*)this ); + sHw = nullptr; +} + +GonkCameraHardware* GonkCameraHardware::sHw = nullptr; +PRUint32 GonkCameraHardware::sHwHandle = 0; + +void +GonkCameraHardware::ReleaseHandle(PRUint32 aHwHandle) +{ + GonkCameraHardware* hw = GetHardware(aHwHandle); + DOM_CAMERA_LOGI("%s: aHwHandle = %d, hw = %p (sHwHandle = %d)\n", __func__, aHwHandle, (void*)hw, sHwHandle); + if (!hw) { + return; + } + + DOM_CAMERA_LOGI("%s: before: sHwHandle = %d\n", __func__, sHwHandle); + sHwHandle += 1; // invalidate old handles before deleting + hw->mClosing = true; + hw->mHardware->disableMsgType(CAMERA_MSG_ALL_MSGS); + hw->mHardware->stopPreview(); + hw->mHardware->release(); + DOM_CAMERA_LOGI("%s: after: sHwHandle = %d\n", __func__, sHwHandle); + delete hw; // destroy the camera hardware instance +} + +PRUint32 +GonkCameraHardware::GetHandle(GonkCamera* aTarget, PRUint32 aCamera) +{ + ReleaseHandle(sHwHandle); + + sHw = new GonkCameraHardware(aTarget, aCamera); + + if (sHw->IsInitialized()) { + return sHwHandle; + } + + DOM_CAMERA_LOGE("failed to initialize camera hardware\n"); + delete sHw; + sHw = nullptr; + return 0; +} + +PRUint32 +GonkCameraHardware::GetFps(PRUint32 aHwHandle) +{ + GonkCameraHardware* hw = GetHardware(aHwHandle); + if (!hw) { + return 0; + } + + return hw->mFps; +} + +void +GonkCameraHardware::GetPreviewSize(PRUint32 aHwHandle, PRUint32* aWidth, PRUint32* aHeight) +{ + GonkCameraHardware* hw = GetHardware(aHwHandle); + if (hw) { + *aWidth = hw->mWidth; + *aHeight = hw->mHeight; + } else { + *aWidth = 0; + *aHeight = 0; + } +} + +void +GonkCameraHardware::SetPreviewSize(PRUint32 aWidth, PRUint32 aHeight) +{ + Vector previewSizes; + PRUint32 bestWidth = aWidth; + PRUint32 bestHeight = aHeight; + PRUint32 minSizeDelta = PR_UINT32_MAX; + PRUint32 delta; + Size size; + + mParams.getSupportedPreviewSizes(previewSizes); + + if (!aWidth && !aHeight) { + // no size specified, take the first supported size + size = previewSizes[0]; + bestWidth = size.width; + bestHeight = size.height; + } else if (aWidth && aHeight) { + // both height and width specified, find the supported size closest to requested size + for (PRUint32 i = 0; i < previewSizes.size(); i++) { + Size size = previewSizes[i]; + PRUint32 delta = abs((long int)(size.width * size.height - aWidth * aHeight)); + if (delta < minSizeDelta) { + minSizeDelta = delta; + bestWidth = size.width; + bestHeight = size.height; + } + } + } else if (!aWidth) { + // width not specified, find closest height match + for (PRUint32 i = 0; i < previewSizes.size(); i++) { + size = previewSizes[i]; + delta = abs((long int)(size.height - aHeight)); + if (delta < minSizeDelta) { + minSizeDelta = delta; + bestWidth = size.width; + bestHeight = size.height; + } + } + } else if (!aHeight) { + // height not specified, find closest width match + for (PRUint32 i = 0; i < previewSizes.size(); i++) { + size = previewSizes[i]; + delta = abs((long int)(size.width - aWidth)); + if (delta < minSizeDelta) { + minSizeDelta = delta; + bestWidth = size.width; + bestHeight = size.height; + } + } + } + + mWidth = bestWidth; + mHeight = bestHeight; + mParams.setPreviewSize(mWidth, mHeight); +} + +void +GonkCameraHardware::SetPreviewSize(PRUint32 aHwHandle, PRUint32 aWidth, PRUint32 aHeight) +{ + GonkCameraHardware* hw = GetHardware(aHwHandle); + if (hw) { + hw->SetPreviewSize(aWidth, aHeight); + } +} + +int +GonkCameraHardware::AutoFocus(PRUint32 aHwHandle) +{ + DOM_CAMERA_LOGI("%s: aHwHandle = %d\n", __func__, aHwHandle); + GonkCameraHardware* hw = GetHardware(aHwHandle); + if (!hw) { + return DEAD_OBJECT; + } + + hw->mHardware->enableMsgType(CAMERA_MSG_FOCUS); + return hw->mHardware->autoFocus(); +} + +void +GonkCameraHardware::CancelAutoFocus(PRUint32 aHwHandle) +{ + DOM_CAMERA_LOGI("%s: aHwHandle = %d\n", __func__, aHwHandle); + GonkCameraHardware* hw = GetHardware(aHwHandle); + if (hw) { + hw->mHardware->cancelAutoFocus(); + } +} + +int +GonkCameraHardware::TakePicture(PRUint32 aHwHandle) +{ + GonkCameraHardware* hw = GetHardware(aHwHandle); + if (!hw) { + return DEAD_OBJECT; + } + + hw->mHardware->enableMsgType(CAMERA_MSG_COMPRESSED_IMAGE); + return hw->mHardware->takePicture(); +} + +void +GonkCameraHardware::CancelTakePicture(PRUint32 aHwHandle) +{ + GonkCameraHardware* hw = GetHardware(aHwHandle); + if (hw) { + hw->mHardware->cancelPicture(); + } +} + +int +GonkCameraHardware::PushParameters(PRUint32 aHwHandle, const CameraParameters& aParams) +{ + GonkCameraHardware* hw = GetHardware(aHwHandle); + if (!hw) { + return DEAD_OBJECT; + } + + return hw->mHardware->setParameters(aParams); +} + +void +GonkCameraHardware::PullParameters(PRUint32 aHwHandle, CameraParameters& aParams) +{ + GonkCameraHardware* hw = GetHardware(aHwHandle); + if (hw) { + aParams = hw->mHardware->getParameters(); + } +} + +int +GonkCameraHardware::StartPreview() +{ + const char* format; + + mHardware->enableMsgType(CAMERA_MSG_PREVIEW_FRAME); + + DOM_CAMERA_LOGI("Preview formats: %s\n", mParams.get(mParams.KEY_SUPPORTED_PREVIEW_FORMATS)); + + // try to set preferred image format and frame rate + const char* const PREVIEW_FORMAT = "yuv420p"; + const char* const BAD_PREVIEW_FORMAT = "yuv420sp"; + mParams.setPreviewFormat(PREVIEW_FORMAT); + mParams.setPreviewFrameRate(mFps); + mHardware->setParameters(mParams); + + // check that our settings stuck + mParams = mHardware->getParameters(); + format = mParams.getPreviewFormat(); + if (strcmp(format, PREVIEW_FORMAT) == 0) { + mPreviewFormat = PREVIEW_FORMAT_YUV420P; /* \o/ */ + } else if (strcmp(format, BAD_PREVIEW_FORMAT) == 0) { + mPreviewFormat = PREVIEW_FORMAT_YUV420SP; + DOM_CAMERA_LOGA("Camera ignored our request for '%s' preview, will have to convert (from %d)\n", PREVIEW_FORMAT, mPreviewFormat); + } else { + mPreviewFormat = PREVIEW_FORMAT_UNKNOWN; + DOM_CAMERA_LOGE("Camera ignored our request for '%s' preview, returned UNSUPPORTED format '%s'\n", PREVIEW_FORMAT, format); + } + + // Check the frame rate and log if the camera ignored our setting + PRUint32 fps = mParams.getPreviewFrameRate(); + if (fps != mFps) { + DOM_CAMERA_LOGA("We asked for %d fps but camera returned %d fps, using it", mFps, fps); + mFps = fps; + } + + return mHardware->startPreview(); +} + +int +GonkCameraHardware::StartPreview(PRUint32 aHwHandle) +{ + GonkCameraHardware* hw = GetHardware(aHwHandle); + DOM_CAMERA_LOGI("%s:%d : aHwHandle = %d, hw = %p\n", __func__, __LINE__, aHwHandle, hw); + if (!hw) { + return DEAD_OBJECT; + } + + return hw->StartPreview(); +} + +void +GonkCameraHardware::StopPreview(PRUint32 aHwHandle) +{ + GonkCameraHardware* hw = GetHardware(aHwHandle); + if (hw) { + hw->mHardware->stopPreview(); + } +} + +PRUint32 +GonkCameraHardware::GetPreviewFormat(PRUint32 aHwHandle) +{ + GonkCameraHardware* hw = GetHardware(aHwHandle); + if (!hw) { + return PREVIEW_FORMAT_UNKNOWN; + } + + return hw->mPreviewFormat; +} diff --git a/dom/camera/GonkCameraHwMgr.h b/dom/camera/GonkCameraHwMgr.h new file mode 100644 index 00000000000..5b85813363e --- /dev/null +++ b/dom/camera/GonkCameraHwMgr.h @@ -0,0 +1,125 @@ +/* + * Copyright (C) 2012 Mozilla Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef DOM_CAMERA_GONKCAMERAHWMGR_H +#define DOM_CAMERA_GONKCAMERAHWMGR_H + +#include "libcameraservice/CameraHardwareInterface.h" +#include "binder/IMemory.h" +#include "mozilla/ReentrantMonitor.h" + +#include "GonkCameraControl.h" + +#define DOM_CAMERA_LOG_LEVEL 3 +#include "CameraCommon.h" + +// config +#define GIHM_TIMING_RECEIVEFRAME 0 +#define GIHM_TIMING_OVERALL 1 + +using namespace mozilla; +using namespace android; + +namespace mozilla { + +typedef class nsGonkCameraControl GonkCamera; + +class GonkCameraHardware +{ +protected: + GonkCameraHardware(GonkCamera* aTarget, PRUint32 aCamera); + ~GonkCameraHardware(); + void init(); + + static void DataCallback(int32_t aMsgType, const sp &aDataPtr, camera_frame_metadata_t* aMetadata, void* aUser); + static void NotifyCallback(int32_t aMsgType, int32_t ext1, int32_t ext2, void* aUser); + +public: + static void ReleaseHandle(PRUint32 aHwHandle); + static PRUint32 GetHandle(GonkCamera* aTarget, PRUint32 aCamera); + static PRUint32 GetFps(PRUint32 aHwHandle); + static void GetPreviewSize(PRUint32 aHwHandle, PRUint32* aWidth, PRUint32* aHeight); + static void SetPreviewSize(PRUint32 aHwHandle, PRUint32 aWidth, PRUint32 aHeight); + static int AutoFocus(PRUint32 aHwHandle); + static void CancelAutoFocus(PRUint32 aHwHandle); + static int TakePicture(PRUint32 aHwHandle); + static void CancelTakePicture(PRUint32 aHwHandle); + static int StartPreview(PRUint32 aHwHandle); + static void StopPreview(PRUint32 aHwHandle); + static int PushParameters(PRUint32 aHwHandle, const CameraParameters& aParams); + static void PullParameters(PRUint32 aHwHandle, CameraParameters& aParams); + + enum { + PREVIEW_FORMAT_UNKNOWN, + PREVIEW_FORMAT_YUV420P, + PREVIEW_FORMAT_YUV420SP + }; + // GetPreviewFormat() MUST be called only after StartPreview(). + static PRUint32 GetPreviewFormat(PRUint32 aHwHandle); + +protected: + static GonkCameraHardware* sHw; + static PRUint32 sHwHandle; + + static GonkCameraHardware* GetHardware(PRUint32 aHwHandle) + { + if (aHwHandle == sHwHandle) { + /** + * In the initial case, sHw will be null and sHwHandle will be 0, + * so even if this function is called with aHwHandle = 0, the + * result will still be null. + */ + return sHw; + } + return nullptr; + } + + // Instance wrappers to make member function access easier. + void SetPreviewSize(PRUint32 aWidth, PRUint32 aHeight); + int StartPreview(); + + PRUint32 mCamera; + PRUint32 mWidth; + PRUint32 mHeight; + PRUint32 mFps; + PRUint32 mPreviewFormat; + bool mClosing; + mozilla::ReentrantMonitor mMonitor; + PRUint32 mNumFrames; + sp mHardware; + GonkCamera* mTarget; + camera_module_t* mModule; + sp mWindow; + CameraParameters mParams; +#if GIHM_TIMING_OVERALL + struct timespec mStart; + struct timespec mAutoFocusStart; +#endif + bool mInitialized; + + bool IsInitialized() + { + return mInitialized; + } + +private: + GonkCameraHardware(const GonkCameraHardware&) MOZ_DELETE; + GonkCameraHardware& operator=(const GonkCameraHardware&) MOZ_DELETE; +}; + +} // namespace mozilla + +#endif // GONK_IMPL_HW_MGR_H diff --git a/dom/camera/GonkCameraManager.cpp b/dom/camera/GonkCameraManager.cpp new file mode 100644 index 00000000000..6911f4c8b77 --- /dev/null +++ b/dom/camera/GonkCameraManager.cpp @@ -0,0 +1,107 @@ +/* + * Copyright (C) 2012 Mozilla Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "jsapi.h" +#include "libcameraservice/CameraHardwareInterface.h" +#include "GonkCameraControl.h" +#include "DOMCameraManager.h" + +#define DOM_CAMERA_LOG_LEVEL 3 +#include "CameraCommon.h" + +// From nsDOMCameraManager, but gonk-specific! + +/* [implicit_jscontext] jsval getListOfCameras (); */ +NS_IMETHODIMP +nsDOMCameraManager::GetListOfCameras(JSContext* cx, JS::Value* _retval) +{ + JSObject* a = JS_NewArrayObject(cx, 0, nullptr); + camera_module_t* module; + PRUint32 index = 0; + PRUint32 count; + + if (!a) { + DOM_CAMERA_LOGE("getListOfCameras : Could not create array object"); + return NS_ERROR_OUT_OF_MEMORY; + } + if (hw_get_module(CAMERA_HARDWARE_MODULE_ID, (const hw_module_t**)&module) < 0) { + DOM_CAMERA_LOGE("getListOfCameras : Could not load camera HAL module"); + return NS_ERROR_NOT_AVAILABLE; + } + + count = module->get_number_of_cameras(); + DOM_CAMERA_LOGI("getListOfCameras : get_number_of_cameras() returned %d\n", count); + while (count--) { + struct camera_info info; + int rv = module->get_camera_info(count, &info); + if (rv != 0) { + DOM_CAMERA_LOGE("getListOfCameras : get_camera_info(%d) failed: %d\n", count, rv); + continue; + } + + JSString* v; + jsval jv; + + switch (info.facing) { + case CAMERA_FACING_BACK: + v = JS_NewStringCopyZ(cx, "back"); + index = 0; + break; + + case CAMERA_FACING_FRONT: + v = JS_NewStringCopyZ(cx, "front"); + index = 1; + break; + + default: + // TODO: handle extra cameras in getCamera(). + { + static PRUint32 extraIndex = 2; + nsCString s; + s.AppendPrintf("extra-camera-%d", count); + v = JS_NewStringCopyZ(cx, s.get()); + index = extraIndex++; + } + break; + } + if (!v) { + DOM_CAMERA_LOGE("getListOfCameras : out of memory populating camera list"); + delete a; + return NS_ERROR_NOT_AVAILABLE; + } + jv = STRING_TO_JSVAL(v); + if (!JS_SetElement(cx, a, index, &jv)) { + DOM_CAMERA_LOGE("getListOfCameras : failed building list of cameras"); + delete a; + return NS_ERROR_NOT_AVAILABLE; + } + } + + *_retval = OBJECT_TO_JSVAL(a); + return NS_OK; +} + +using namespace mozilla; + +NS_IMETHODIMP +GetCameraTask::Run() +{ + nsCOMPtr cameraControl = new nsGonkCameraControl(mCameraId, mCameraThread); + + DOM_CAMERA_LOGI("%s:%d\n", __func__, __LINE__); + + return NS_DispatchToMainThread(new GetCameraResult(cameraControl, mOnSuccessCb)); +} diff --git a/dom/camera/GonkCameraPreview.cpp b/dom/camera/GonkCameraPreview.cpp new file mode 100644 index 00000000000..81960237b3e --- /dev/null +++ b/dom/camera/GonkCameraPreview.cpp @@ -0,0 +1,197 @@ +/* + * Copyright (C) 2012 Mozilla Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "VideoUtils.h" +#include "GonkCameraHwMgr.h" +#include "GonkCameraPreview.h" + +#define DOM_CAMERA_LOG_LEVEL 2 +#include "CameraCommon.h" + +using namespace mozilla; + +/** + * This big macro takes two 32-bit input blocks of interlaced u and + * v data (from a yuv420sp frame) in 's0' and 's1', and deinterlaces + * them into pairs of contiguous 32-bit blocks, the u plane data in + * 'u', and the v plane data in 'v' (i.e. for a yuv420p frame). + * + * yuv420sp: + * [ y-data ][ uv-data ] + * [ uv-data ]: [u0][v0][u1][v1][u2][v2]... + * + * yuv420p: + * [ y-data ][ u-data ][ v-data ] + * [ u-data ]: [u0][u1][u2]... + * [ v-data ]: [v0][v1][v2]... + * + * Doing this in 32-bit blocks is significantly faster than using + * byte-wise operations on ARM. (In some cases, the byte-wise + * de-interlacing can be too slow to keep up with the preview frames + * coming from the driver. + */ +#define DEINTERLACE( u, v, s0, s1 ) \ + u = ( (s0) & 0xFF00UL ) >> 8 | ( (s0) & 0xFF000000UL ) >> 16; \ + u |= ( (s1) & 0xFF00UL ) << 8 | ( (s1) & 0xFF000000UL ); \ + v = ( (s0) & 0xFFUL ) | ( (s0) & 0xFF0000UL ) >> 8; \ + v |= ( (s1) & 0xFFUL ) << 16 | ( (s1) & 0xFF0000UL ) << 8; + +void +GonkCameraPreview::ReceiveFrame(PRUint8 *aData, PRUint32 aLength) +{ + DOM_CAMERA_LOGI("%s:%d : this=%p\n", __func__, __LINE__, this); + + if (mInput->HaveEnoughBuffered(TRACK_VIDEO)) { + if (mDiscardedFrameCount == 0) { + DOM_CAMERA_LOGI("mInput has enough data buffered, starting to discard\n"); + } + ++mDiscardedFrameCount; + return; + } else if (mDiscardedFrameCount) { + DOM_CAMERA_LOGI("mInput needs more data again; discarded %d frames in a row\n", mDiscardedFrameCount); + mDiscardedFrameCount = 0; + } + + switch (mFormat) { + case GonkCameraHardware::PREVIEW_FORMAT_YUV420SP: + { + // de-interlace the u and v planes + uint8_t* y = aData; + uint32_t yN = mWidth * mHeight; + + NS_ASSERTION(yN & 0x3 == 0, "Invalid image dimensions!"); + + uint32_t uvN = yN / 4; + uint32_t* src = (uint32_t*)( y + yN ); + uint32_t* d = new uint32_t[ uvN / 2 ]; + uint32_t* u = d; + uint32_t* v = u + uvN / 4; + + // we're handling pairs of 32-bit words, so divide by 8 + NS_ASSERTION(uvN & 0x7 == 0, "Invalid image dimensions!"); + uvN /= 8; + + while (uvN--) { + uint32_t src0 = *src++; + uint32_t src1 = *src++; + + uint32_t u0; + uint32_t v0; + uint32_t u1; + uint32_t v1; + + DEINTERLACE( u0, v0, src0, src1 ); + + src0 = *src++; + src1 = *src++; + + DEINTERLACE( u1, v1, src0, src1 ); + + *u++ = u0; + *u++ = u1; + *v++ = v0; + *v++ = v1; + } + + memcpy(y + yN, d, yN / 2); + delete[] d; + } + break; + + case GonkCameraHardware::PREVIEW_FORMAT_YUV420P: + // no transformating required + break; + + default: + // in a format we don't handle, get out of here + return; + } + + Image::Format format = Image::PLANAR_YCBCR; + nsRefPtr image = mImageContainer->CreateImage(&format, 1); + image->AddRef(); + PlanarYCbCrImage* videoImage = static_cast(image.get()); + + /** + * If you change either lumaBpp or chromaBpp, make sure the + * assertions below still hold. + */ + const PRUint8 lumaBpp = 8; + const PRUint8 chromaBpp = 4; + PlanarYCbCrImage::Data data; + data.mYChannel = aData; + data.mYSize = gfxIntSize(mWidth, mHeight); + + data.mYStride = mWidth * lumaBpp; + NS_ASSERTION(data.mYStride & 0x7 == 0, "Invalid image dimensions!"); + data.mYStride /= 8; + + data.mCbCrStride = mWidth * chromaBpp; + NS_ASSERTION(data.mCbCrStride & 0x7 == 0, "Invalid image dimensions!"); + data.mCbCrStride /= 8; + + data.mCbChannel = aData + mHeight * data.mYStride; + data.mCrChannel = data.mCbChannel + mHeight * data.mCbCrStride / 2; + data.mCbCrSize = gfxIntSize(mWidth / 2, mHeight / 2); + data.mPicX = 0; + data.mPicY = 0; + data.mPicSize = gfxIntSize(mWidth, mHeight); + data.mStereoMode = mozilla::layers::STEREO_MODE_MONO; + videoImage->SetData(data); // copies buffer + + mVideoSegment.AppendFrame(videoImage, 1, gfxIntSize(mWidth, mHeight)); + mInput->AppendToTrack(TRACK_VIDEO, &mVideoSegment); + + mFrameCount += 1; + + if ((mFrameCount % 10) == 0) { + DOM_CAMERA_LOGI("%s:%d : mFrameCount = %d\n", __func__, __LINE__, mFrameCount); + } +} + +void +GonkCameraPreview::Start() +{ + DOM_CAMERA_LOGI("%s:%d : this=%p\n", __func__, __LINE__, this); + + /** + * We set and then immediately get the preview size, in case the camera + * driver has decided to ignore our given dimensions. We need to know + * the dimensions the driver is using so that, if needed, we can properly + * de-interlace the yuv420sp format in ReceiveFrame() above. + */ + GonkCameraHardware::SetPreviewSize(mHwHandle, mWidth, mHeight); + GonkCameraHardware::GetPreviewSize(mHwHandle, &mWidth, &mHeight); + SetFrameRate(GonkCameraHardware::GetFps(mHwHandle)); + + if (GonkCameraHardware::StartPreview(mHwHandle) == OK) { + // GetPreviewFormat() must be called after StartPreview(). + mFormat = GonkCameraHardware::GetPreviewFormat(mHwHandle); + DOM_CAMERA_LOGI("preview stream is (actually!) %d x %d (w x h), %d frames per second, format %d\n", mWidth, mHeight, mFramesPerSecond, mFormat); + } else { + DOM_CAMERA_LOGE("%s: failed to start preview\n", __func__); + } +} + +void +GonkCameraPreview::Stop() +{ + DOM_CAMERA_LOGI("%s:%d : this=%p\n", __func__, __LINE__, this); + + GonkCameraHardware::StopPreview(mHwHandle); + mInput->EndTrack(TRACK_VIDEO); + mInput->Finish(); +} diff --git a/dom/camera/GonkCameraPreview.h b/dom/camera/GonkCameraPreview.h new file mode 100644 index 00000000000..1999976046f --- /dev/null +++ b/dom/camera/GonkCameraPreview.h @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2012 Mozilla Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef DOM_CAMERA_GONKCAMERAPREVIEW_H +#define DOM_CAMERA_GONKCAMERAPREVIEW_H + +#include "CameraPreview.h" + +#define DOM_CAMERA_LOG_LEVEL 3 +#include "CameraCommon.h" + +namespace mozilla { + +class GonkCameraPreview : public CameraPreview +{ +public: + GonkCameraPreview(PRUint32 aHwHandle, PRUint32 aWidth, PRUint32 aHeight) + : CameraPreview(aWidth, aHeight) + , mHwHandle(aHwHandle) + , mDiscardedFrameCount(0) + , mFormat(GonkCameraHardware::PREVIEW_FORMAT_UNKNOWN) + { } + + void ReceiveFrame(PRUint8 *aData, PRUint32 aLength); + + void Start(); + void Stop(); + +protected: + ~GonkCameraPreview() + { + Stop(); + } + + PRUint32 mHwHandle; + PRUint32 mDiscardedFrameCount; + PRUint32 mFormat; + +private: + GonkCameraPreview(const GonkCameraPreview&) MOZ_DELETE; + GonkCameraPreview& operator=(const GonkCameraPreview&) MOZ_DELETE; +}; + +} // namespace mozilla + +#endif // DOM_CAMERA_GONKCAMERAPREVIEW_H diff --git a/dom/camera/GonkNativeWindow.cpp b/dom/camera/GonkNativeWindow.cpp new file mode 100644 index 00000000000..163517f3216 --- /dev/null +++ b/dom/camera/GonkNativeWindow.cpp @@ -0,0 +1,475 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * Copyright (C) 2012 Mozilla Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "GonkNativeWindow.h" +#include "nsDebug.h" + +// enable debug logging by setting to 1 +#define CNW_DEBUG 0 +#if CNW_DEBUG +#define CNW_LOGD(...) {(void)printf_stderr(__VA_ARGS__);} +#else +#define CNW_LOGD(...) ((void)0) +#endif + +#define CNW_LOGE(...) {(void)printf_stderr(__VA_ARGS__);} + +using namespace android; + +GonkNativeWindow::GonkNativeWindow() +{ + GonkNativeWindow::init(); +} + +GonkNativeWindow::~GonkNativeWindow() +{ + freeAllBuffersLocked(); +} + +void GonkNativeWindow::init() +{ + // Initialize the ANativeWindow function pointers. + ANativeWindow::setSwapInterval = hook_setSwapInterval; + ANativeWindow::dequeueBuffer = hook_dequeueBuffer; + ANativeWindow::cancelBuffer = hook_cancelBuffer; + ANativeWindow::lockBuffer = hook_lockBuffer; + ANativeWindow::queueBuffer = hook_queueBuffer; + ANativeWindow::query = hook_query; + ANativeWindow::perform = hook_perform; + + mDefaultWidth = 0; + mDefaultHeight = 0; + mPixelFormat = 0; + mUsage = 0; + mTimestamp = NATIVE_WINDOW_TIMESTAMP_AUTO; + mBufferCount = MIN_BUFFER_SLOTS; + mFrameCounter = 0; +} + + +int GonkNativeWindow::hook_setSwapInterval(ANativeWindow* window, int interval) +{ + GonkNativeWindow* c = getSelf(window); + return c->setSwapInterval(interval); +} + +int GonkNativeWindow::hook_dequeueBuffer(ANativeWindow* window, + ANativeWindowBuffer** buffer) +{ + GonkNativeWindow* c = getSelf(window); + return c->dequeueBuffer(buffer); +} + +int GonkNativeWindow::hook_cancelBuffer(ANativeWindow* window, + ANativeWindowBuffer* buffer) +{ + GonkNativeWindow* c = getSelf(window); + return c->cancelBuffer(buffer); +} + +int GonkNativeWindow::hook_lockBuffer(ANativeWindow* window, + ANativeWindowBuffer* buffer) +{ + GonkNativeWindow* c = getSelf(window); + return c->lockBuffer(buffer); +} + +int GonkNativeWindow::hook_queueBuffer(ANativeWindow* window, + ANativeWindowBuffer* buffer) +{ + GonkNativeWindow* c = getSelf(window); + return c->queueBuffer(buffer); +} + +int GonkNativeWindow::hook_query(const ANativeWindow* window, + int what, int* value) +{ + const GonkNativeWindow* c = getSelf(window); + return c->query(what, value); +} + +int GonkNativeWindow::hook_perform(ANativeWindow* window, int operation, ...) +{ + va_list args; + va_start(args, operation); + GonkNativeWindow* c = getSelf(window); + return c->perform(operation, args); +} + +void GonkNativeWindow::freeBufferLocked(int i) +{ + if (mSlots[i].mGraphicBuffer != NULL) { + mSlots[i].mGraphicBuffer.clear(); + mSlots[i].mGraphicBuffer = NULL; + } + mSlots[i].mBufferState = BufferSlot::FREE; +} + +void GonkNativeWindow::freeAllBuffersLocked() +{ + for (int i = 0; i < NUM_BUFFER_SLOTS; i++) { + freeBufferLocked(i); + } +} + +int GonkNativeWindow::setBufferCount(int bufferCount) { + CNW_LOGD("setBufferCount: count=%d", bufferCount); + Mutex::Autolock lock(mMutex); + + if (bufferCount > NUM_BUFFER_SLOTS) { + CNW_LOGE("setBufferCount: bufferCount larger than slots available"); + return BAD_VALUE; + } + + // special-case, nothing to do + if (bufferCount == mBufferCount) { + return OK; + } + + if (bufferCount < MIN_BUFFER_SLOTS) { + CNW_LOGE("setBufferCount: requested buffer count (%d) is less than " + "minimum (%d)", bufferCount, MIN_BUFFER_SLOTS); + return BAD_VALUE; + } + + // Error out if the user has dequeued buffers + for (int i=0 ; i mBufferCount) { + // easy, we just have more buffers + mBufferCount = bufferCount; + mDequeueCondition.signal(); + return OK; + } + + // reducing the number of buffers + // here we're guaranteed that the client doesn't have dequeued buffers + // and will release all of its buffer references. + freeAllBuffersLocked(); + mBufferCount = bufferCount; + mDequeueCondition.signal(); + return OK; +} + +int GonkNativeWindow::dequeueBuffer(android_native_buffer_t** buffer) +{ + Mutex::Autolock lock(mMutex); + + int found = -1; + int dequeuedCount = 0; + bool tryAgain = true; + + CNW_LOGD("dequeueBuffer: E"); + while (tryAgain) { + // look for a free buffer to give to the client + found = INVALID_BUFFER_SLOT; + dequeuedCount = 0; + for (int i = 0; i < mBufferCount; i++) { + const int state = mSlots[i].mBufferState; + if (state == BufferSlot::DEQUEUED) { + dequeuedCount++; + } + else if (state == BufferSlot::FREE) { + /* We return the oldest of the free buffers to avoid + * stalling the producer if possible. This is because + * the consumer may still have pending reads of the + * buffers in flight. + */ + bool isOlder = mSlots[i].mFrameNumber < mSlots[found].mFrameNumber; + if (found < 0 || isOlder) { + found = i; + } + } + } + + // we're in synchronous mode and didn't find a buffer, we need to + // wait for some buffers to be consumed + tryAgain = (found == INVALID_BUFFER_SLOT); + if (tryAgain) { + mDequeueCondition.wait(mMutex); + } + } + + if (found == INVALID_BUFFER_SLOT) { + // This should not happen. + CNW_LOGE("dequeueBuffer: no available buffer slots"); + return -EBUSY; + } + + const int buf = found; + + // buffer is now in DEQUEUED + mSlots[buf].mBufferState = BufferSlot::DEQUEUED; + + const sp& gbuf(mSlots[buf].mGraphicBuffer); + + if (gbuf == NULL) { + status_t error; + sp graphicBuffer( new GraphicBuffer( mDefaultWidth, mDefaultHeight, mPixelFormat, mUsage)); + error = graphicBuffer->initCheck(); + if (error != NO_ERROR) { + CNW_LOGE("dequeueBuffer: createGraphicBuffer failed with error %d",error); + return error; + } + mSlots[buf].mGraphicBuffer = graphicBuffer; + } + *buffer = mSlots[buf].mGraphicBuffer.get(); + + CNW_LOGD("dequeueBuffer: returning slot=%d buf=%p ", buf, + mSlots[buf].mGraphicBuffer->handle ); + + CNW_LOGD("dequeueBuffer: X"); + return NO_ERROR; +} + +int GonkNativeWindow::getSlotFromBufferLocked( + android_native_buffer_t* buffer) const +{ + if (buffer == NULL) { + CNW_LOGE("getSlotFromBufferLocked: encountered NULL buffer"); + return BAD_VALUE; + } + + for (int i = 0; i < NUM_BUFFER_SLOTS; i++) { + if (mSlots[i].mGraphicBuffer != NULL && mSlots[i].mGraphicBuffer->handle == buffer->handle) { + return i; + } + } + CNW_LOGE("getSlotFromBufferLocked: unknown buffer: %p", buffer->handle); + return BAD_VALUE; +} + +int GonkNativeWindow::queueBuffer(ANativeWindowBuffer* buffer) +{ + Mutex::Autolock lock(mMutex); + CNW_LOGD("queueBuffer: E"); + int buf = getSlotFromBufferLocked(buffer); + + if (buf < 0 || buf >= mBufferCount) { + CNW_LOGE("queueBuffer: slot index out of range [0, %d]: %d", + mBufferCount, buf); + return -EINVAL; + } else if (mSlots[buf].mBufferState != BufferSlot::DEQUEUED) { + CNW_LOGE("queueBuffer: slot %d is not owned by the client " + "(state=%d)", buf, mSlots[buf].mBufferState); + return -EINVAL; + } + + int64_t timestamp; + if (mTimestamp == NATIVE_WINDOW_TIMESTAMP_AUTO) { + timestamp = systemTime(SYSTEM_TIME_MONOTONIC); + } else { + timestamp = mTimestamp; + } + + // Set the state to FREE as there are no operations on the queued buffer + // And, so that the buffer can be dequeued when needed. + mSlots[buf].mBufferState = BufferSlot::FREE; + mSlots[buf].mTimestamp = timestamp; + mFrameCounter++; + mSlots[buf].mFrameNumber = mFrameCounter; + + mDequeueCondition.signal(); + CNW_LOGD("queueBuffer: X"); + + return OK; +} + +int GonkNativeWindow::lockBuffer(ANativeWindowBuffer* buffer) +{ + CNW_LOGD("GonkNativeWindow::lockBuffer"); + Mutex::Autolock lock(mMutex); + return OK; +} + +int GonkNativeWindow::cancelBuffer(ANativeWindowBuffer* buffer) +{ + Mutex::Autolock lock(mMutex); + int buf = getSlotFromBufferLocked(buffer); + + CNW_LOGD("cancelBuffer: slot=%d", buf); + if (buf < 0 || buf >= mBufferCount) { + CNW_LOGE("cancelBuffer: slot index out of range [0, %d]: %d", + mBufferCount, buf); + return -EINVAL; + } else if (mSlots[buf].mBufferState != BufferSlot::DEQUEUED) { + CNW_LOGE("cancelBuffer: slot %d is not owned by the client (state=%d)", + buf, mSlots[buf].mBufferState); + return -EINVAL; + } + mSlots[buf].mBufferState = BufferSlot::FREE; + mSlots[buf].mFrameNumber = 0; + mDequeueCondition.signal(); + return OK; +} + +int GonkNativeWindow::perform(int operation, va_list args) +{ + switch (operation) { + case NATIVE_WINDOW_CONNECT: + // deprecated. must return NO_ERROR. + return NO_ERROR; + case NATIVE_WINDOW_DISCONNECT: + // deprecated. must return NO_ERROR. + return NO_ERROR; + case NATIVE_WINDOW_SET_USAGE: + return dispatchSetUsage(args); + case NATIVE_WINDOW_SET_BUFFER_COUNT: + return dispatchSetBufferCount(args); + case NATIVE_WINDOW_SET_BUFFERS_GEOMETRY: + return dispatchSetBuffersGeometry(args); + case NATIVE_WINDOW_SET_BUFFERS_TIMESTAMP: + return dispatchSetBuffersTimestamp(args); + case NATIVE_WINDOW_SET_BUFFERS_DIMENSIONS: + return dispatchSetBuffersDimensions(args); + case NATIVE_WINDOW_SET_BUFFERS_FORMAT: + return dispatchSetBuffersFormat(args); + case NATIVE_WINDOW_SET_CROP: + case NATIVE_WINDOW_SET_BUFFERS_TRANSFORM: + case NATIVE_WINDOW_SET_SCALING_MODE: + case NATIVE_WINDOW_LOCK: + case NATIVE_WINDOW_UNLOCK_AND_POST: + case NATIVE_WINDOW_API_CONNECT: + case NATIVE_WINDOW_API_DISCONNECT: + default: + return INVALID_OPERATION; + } +} + +int GonkNativeWindow::query(int what, int* outValue) const +{ + Mutex::Autolock lock(mMutex); + + int value; + switch (what) { + case NATIVE_WINDOW_WIDTH: + value = mDefaultWidth; + break; + case NATIVE_WINDOW_HEIGHT: + value = mDefaultHeight; + break; + case NATIVE_WINDOW_FORMAT: + value = mPixelFormat; + break; + case NATIVE_WINDOW_MIN_UNDEQUEUED_BUFFERS: + value = MIN_UNDEQUEUED_BUFFERS; + break; + default: + return BAD_VALUE; + } + outValue[0] = value; + return NO_ERROR; +} + +int GonkNativeWindow::setSwapInterval(int interval) +{ + return NO_ERROR; +} + +int GonkNativeWindow::dispatchSetUsage(va_list args) +{ + int usage = va_arg(args, int); + return setUsage(usage); +} + +int GonkNativeWindow::dispatchSetBufferCount(va_list args) +{ + size_t bufferCount = va_arg(args, size_t); + return setBufferCount(bufferCount); +} + +int GonkNativeWindow::dispatchSetBuffersGeometry(va_list args) +{ + int w = va_arg(args, int); + int h = va_arg(args, int); + int f = va_arg(args, int); + int err = setBuffersDimensions(w, h); + if (err != 0) { + return err; + } + return setBuffersFormat(f); +} + +int GonkNativeWindow::dispatchSetBuffersDimensions(va_list args) +{ + int w = va_arg(args, int); + int h = va_arg(args, int); + return setBuffersDimensions(w, h); +} + +int GonkNativeWindow::dispatchSetBuffersFormat(va_list args) +{ + int f = va_arg(args, int); + return setBuffersFormat(f); +} + +int GonkNativeWindow::dispatchSetBuffersTimestamp(va_list args) +{ + int64_t timestamp = va_arg(args, int64_t); + return setBuffersTimestamp(timestamp); +} + +int GonkNativeWindow::setUsage(uint32_t reqUsage) +{ + CNW_LOGD("GonkNativeWindow::setUsage"); + Mutex::Autolock lock(mMutex); + mUsage = reqUsage; + return OK; +} + +int GonkNativeWindow::setBuffersDimensions(int w, int h) +{ + CNW_LOGD("GonkNativeWindow::setBuffersDimensions"); + Mutex::Autolock lock(mMutex); + + if (w<0 || h<0) + return BAD_VALUE; + + if ((w && !h) || (!w && h)) + return BAD_VALUE; + + mDefaultWidth = w; + mDefaultHeight = h; + + return OK; +} + +int GonkNativeWindow::setBuffersFormat(int format) +{ + CNW_LOGD("GonkNativeWindow::setBuffersFormat"); + Mutex::Autolock lock(mMutex); + + if (format<0) + return BAD_VALUE; + + mPixelFormat = format; + + return NO_ERROR; +} + +int GonkNativeWindow::setBuffersTimestamp(int64_t timestamp) +{ + CNW_LOGD("GonkNativeWindow::setBuffersTimestamp"); + Mutex::Autolock lock(mMutex); + mTimestamp = timestamp; + return OK; +} diff --git a/dom/camera/GonkNativeWindow.h b/dom/camera/GonkNativeWindow.h new file mode 100644 index 00000000000..310dea55048 --- /dev/null +++ b/dom/camera/GonkNativeWindow.h @@ -0,0 +1,192 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * Copyright (C) 2012 Mozilla Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef DOM_CAMERA_GONKNATIVEWINDOW_H +#define DOM_CAMERA_GONKNATIVEWINDOW_H + +#include +#include + +#include + +#include +#include + +#include +#include +#include +#include + +namespace android { + +class GonkNativeWindow : public EGLNativeBase +{ +public: + enum { MIN_UNDEQUEUED_BUFFERS = 2 }; + enum { MIN_BUFFER_SLOTS = MIN_UNDEQUEUED_BUFFERS }; + enum { NUM_BUFFER_SLOTS = 32 }; + + GonkNativeWindow(); + ~GonkNativeWindow(); // this class cannot be overloaded + + // ANativeWindow hooks + static int hook_cancelBuffer(ANativeWindow* window, ANativeWindowBuffer* buffer); + static int hook_dequeueBuffer(ANativeWindow* window, ANativeWindowBuffer** buffer); + static int hook_lockBuffer(ANativeWindow* window, ANativeWindowBuffer* buffer); + static int hook_perform(ANativeWindow* window, int operation, ...); + static int hook_query(const ANativeWindow* window, int what, int* value); + static int hook_queueBuffer(ANativeWindow* window, ANativeWindowBuffer* buffer); + static int hook_setSwapInterval(ANativeWindow* window, int interval); + +protected: + virtual int cancelBuffer(ANativeWindowBuffer* buffer); + virtual int dequeueBuffer(ANativeWindowBuffer** buffer); + virtual int lockBuffer(ANativeWindowBuffer* buffer); + virtual int perform(int operation, va_list args); + virtual int query(int what, int* value) const; + virtual int queueBuffer(ANativeWindowBuffer* buffer); + virtual int setSwapInterval(int interval); + + virtual int setBufferCount(int bufferCount); + virtual int setBuffersDimensions(int w, int h); + virtual int setBuffersFormat(int format); + virtual int setBuffersTimestamp(int64_t timestamp); + virtual int setUsage(uint32_t reqUsage); + + // freeBufferLocked frees the resources (both GraphicBuffer and EGLImage) + // for the given slot. + void freeBufferLocked(int index); + + // freeAllBuffersLocked frees the resources (both GraphicBuffer and + // EGLImage) for all slots. + void freeAllBuffersLocked(); + +private: + void init(); + + int dispatchSetBufferCount(va_list args); + int dispatchSetBuffersGeometry(va_list args); + int dispatchSetBuffersDimensions(va_list args); + int dispatchSetBuffersFormat(va_list args); + int dispatchSetBuffersTimestamp(va_list args); + int dispatchSetUsage(va_list args); + + int getSlotFromBufferLocked(android_native_buffer_t* buffer) const; + +private: + enum { INVALID_BUFFER_SLOT = -1 }; + + struct BufferSlot { + + BufferSlot() + : mGraphicBuffer(0), + mBufferState(BufferSlot::FREE), + mTimestamp(0), + mFrameNumber(0){ + } + + // mGraphicBuffer points to the buffer allocated for this slot or is NULL + // if no buffer has been allocated. + sp mGraphicBuffer; + + // BufferState represents the different states in which a buffer slot + // can be. + enum BufferState { + // FREE indicates that the buffer is not currently being used and + // will not be used in the future until it gets dequeued and + // subsequently queued by the client. + FREE = 0, + + // DEQUEUED indicates that the buffer has been dequeued by the + // client, but has not yet been queued or canceled. The buffer is + // considered 'owned' by the client, and the server should not use + // it for anything. + // + // Note that when in synchronous-mode (mSynchronousMode == true), + // the buffer that's currently attached to the texture may be + // dequeued by the client. That means that the current buffer can + // be in either the DEQUEUED or QUEUED state. In asynchronous mode, + // however, the current buffer is always in the QUEUED state. + DEQUEUED = 1, + + // QUEUED indicates that the buffer has been queued by the client, + // and has not since been made available for the client to dequeue. + // Attaching the buffer to the texture does NOT transition the + // buffer away from the QUEUED state. However, in Synchronous mode + // the current buffer may be dequeued by the client under some + // circumstances. See the note about the current buffer in the + // documentation for DEQUEUED. + QUEUED = 2, + }; + + // mBufferState is the current state of this buffer slot. + BufferState mBufferState; + + // mTimestamp is the current timestamp for this buffer slot. This gets + // to set by queueBuffer each time this slot is queued. + int64_t mTimestamp; + + // mFrameNumber is the number of the queued frame for this slot. + uint64_t mFrameNumber; + }; + + // mSlots is the array of buffer slots that must be mirrored on the client + // side. This allows buffer ownership to be transferred between the client + // and server without sending a GraphicBuffer over binder. The entire array + // is initialized to NULL at construction time, and buffers are allocated + // for a slot when requestBuffer is called with that slot's index. + BufferSlot mSlots[NUM_BUFFER_SLOTS]; + + // mDequeueCondition condition used for dequeueBuffer in synchronous mode + mutable Condition mDequeueCondition; + + // mTimestamp is the timestamp that will be used for the next buffer queue + // operation. It defaults to NATIVE_WINDOW_TIMESTAMP_AUTO, which means that + // a timestamp is auto-generated when queueBuffer is called. + int64_t mTimestamp; + + // mDefaultWidth holds the default width of allocated buffers. It is used + // in requestBuffers() if a width and height of zero is specified. + uint32_t mDefaultWidth; + + // mDefaultHeight holds the default height of allocated buffers. It is used + // in requestBuffers() if a width and height of zero is specified. + uint32_t mDefaultHeight; + + // mPixelFormat holds the pixel format of allocated buffers. It is used + // in requestBuffers() if a format of zero is specified. + uint32_t mPixelFormat; + + // usage flag + uint32_t mUsage; + + // mBufferCount is the number of buffer slots that the client and server + // must maintain. It defaults to MIN_ASYNC_BUFFER_SLOTS and can be changed + // by calling setBufferCount or setBufferCountServer + int mBufferCount; + + // mMutex is the mutex used to prevent concurrent access to the member + // variables. It must be locked whenever the member variables are accessed. + mutable Mutex mMutex; + + // mFrameCounter is the free running counter, incremented for every buffer queued + uint64_t mFrameCounter; +}; + +}; // namespace android + +#endif // DOM_CAMERA_GONKNATIVEWINDOW_H diff --git a/dom/camera/Makefile.in b/dom/camera/Makefile.in new file mode 100644 index 00000000000..a86c3ff7500 --- /dev/null +++ b/dom/camera/Makefile.in @@ -0,0 +1,52 @@ +# 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/. + +DEPTH = ../.. +topsrcdir = @top_srcdir@ +srcdir = @srcdir@ +VPATH = @srcdir@ + +include $(DEPTH)/config/autoconf.mk + +MODULE = dom +LIBRARY_NAME = domcamera_s +XPIDL_MODULE = dom_camera +LIBXUL_LIBRARY = 1 +FORCE_STATIC_LIB = 1 + +include $(topsrcdir)/dom/dom-config.mk + +CPPSRCS = \ + DOMCameraManager.cpp \ + CameraControl.cpp \ + CameraPreview.cpp \ + $(NULL) + +ifeq ($(MOZ_B2G_CAMERA),1) +CPPSRCS += \ + GonkCameraManager.cpp \ + GonkCameraControl.cpp \ + GonkCameraHwMgr.cpp \ + GonkCameraPreview.cpp \ + GonkNativeWindow.cpp \ + GonkCameraCapabilities.cpp \ + $(NULL) +else +CPPSRCS += \ + FallbackCameraManager.cpp \ + FallbackCameraControl.cpp \ + FallbackCameraCapabilities.cpp \ + $(NULL) +endif + +XPIDLSRCS = \ + nsIDOMNavigatorCamera.idl \ + nsIDOMCameraManager.idl \ + $(NULL) + +EXPORTS = \ + DOMCameraManager.h \ + $(NULL) + +include $(topsrcdir)/config/rules.mk diff --git a/dom/camera/nsIDOMCameraManager.idl b/dom/camera/nsIDOMCameraManager.idl new file mode 100644 index 00000000000..9863658dd66 --- /dev/null +++ b/dom/camera/nsIDOMCameraManager.idl @@ -0,0 +1,341 @@ +#include "domstubs.idl" + +#include "nsIDOMMediaStream.idl" +#include "nsIDOMDOMRequest.idl" + + +interface nsIDOMBlob; + +/* Used to set the dimensions of a captured picture, + a preview stream, a video capture stream, etc. */ +dictionary CameraSize { + unsigned long width; + unsigned long height; +}; + +/* Camera regions are used to set focus and metering areas; + the coordinates are referenced to the sensor: + (-1000, -1000) is the top left corner + (1000, 1000) is the bottom left corner + The weight of the region can range from 0 to 1000. */ +dictionary CameraRegion { + long top; + long left; + long bottom; + long right; + unsigned long weight; +}; + +/* The position information to record in the image header. + 'NaN' indicates the information is not available. */ +dictionary CameraPosition { + double latitude; + double longitude; + double altitude; + double timestamp; +}; + +/* Select a camera to use. */ +dictionary CameraSelector { + DOMString camera = "back"; +}; + +[scriptable, uuid(64196840-0d03-4b65-a955-790f43a4b810)] +interface nsICameraCapabilities : nsISupports +{ + /* an array of objects with 'height' and 'width' properties + supported for the preview stream */ + [implicit_jscontext] + readonly attribute jsval previewSizes; + + /* an array of objects with 'height' and 'width' properties + supported for picture taking */ + [implicit_jscontext] + readonly attribute jsval pictureSizes; + + /* an array of strings, e.g. [ "jpeg", "rgb565" ] */ + [implicit_jscontext] + readonly attribute jsval fileFormats; + + /* an array of strings, e.g. [ "auto", "fluorescent", etc. ] */ + [implicit_jscontext] + readonly attribute jsval whiteBalanceModes; + + /* an array of strings, e.g. [ "auto", "night", "beach", etc. ] */ + [implicit_jscontext] + readonly attribute jsval sceneModes; + + /* an array of strings, e.g. [ "normal", "sepia", "mono", etc. ] */ + [implicit_jscontext] + readonly attribute jsval effects; + + /* an array of strings, e.g. [ "auto", "off", "on", etc. ] */ + [implicit_jscontext] + readonly attribute jsval flashModes; + + /* an array of strings, e.g. [ "auto", "fixed", "macro", etc. ] */ + [implicit_jscontext] + readonly attribute jsval focusModes; + + /* the maximum number of focus areas supported by the camera */ + [implicit_jscontext] + readonly attribute long maxFocusAreas; + + /* the minimum supported exposure compensation value */ + [implicit_jscontext] + readonly attribute double minExposureCompensation; + + /* the maximum supported exposure compensation value */ + [implicit_jscontext] + readonly attribute double maxExposureCompensation; + + /* exposure compensation minimum step-size */ + [implicit_jscontext] + readonly attribute double stepExposureCompensation; + + /* the maximum number of metering areas supported by the camera */ + [implicit_jscontext] + readonly attribute long maxMeteringAreas; + + /* an array of doubles, e.g. [ 1.0, 1.2, 1.5, 2.0, 3.0, etc. ], + or null if zooming is not supported */ + [implicit_jscontext] + readonly attribute jsval zoomRatios; + + /* an array of objects with 'height' and 'width' properties + supported for video recording */ + [implicit_jscontext] + readonly attribute jsval videoSizes; +}; + +/* + These properties only affect the captured image; + invalid property settings are ignored. +*/ +dictionary CameraPictureOptions +{ + /* an object with a combination of 'height' and 'width' properties + chosen from nsICameraCapabilities.pictureSizes */ + jsval pictureSize; + + /* one of the file formats chosen from + nsICameraCapabilities.fileFormats */ + DOMString fileFormat; + + /* the rotation of the image in degrees, from 0 to 270 in + steps of 90; this doesn't affect the image, only the + rotation recorded in the image header.*/ + long rotation; + + /* an object containing any or all of 'latitude', 'longitude', + 'altitude', and 'timestamp', used to record when and where + the image was taken. e.g. + { + latitude: 43.647118, + longitude: -79.3943, + altitude: 500 + // timestamp not specified, in this case, and + // won't be included in the image header + } + + can be null in the case where position information isn't + available/desired. + + 'altitude' is in metres; 'timestamp' is UTC, in seconds from + January 1, 1970. + */ + jsval position; +}; + +[scriptable, function, uuid(0444a687-4bc9-462c-8246-5423f0fe46a4)] +interface nsICameraPreviewStreamCallback : nsISupports +{ + void handleEvent(in nsIDOMMediaStream stream); +}; + +[scriptable, function, uuid(6baa4ac7-9c25-4c48-9bb0-5193b38b9b0a)] +interface nsICameraAutoFocusCallback : nsISupports +{ + void handleEvent(in boolean success); +}; + +[scriptable, function, uuid(17af779e-cb6f-4ca5-890c-06468ff82e4f)] +interface nsICameraTakePictureCallback : nsISupports +{ + void handleEvent(in nsIDOMBlob picture); +}; + +[scriptable, function, uuid(ac43f123-529c-48d3-84dd-ad206b7aca9b)] +interface nsICameraStartRecordingCallback : nsISupports +{ + void handleEvent(in nsIDOMMediaStream stream); +}; + +[scriptable, function, uuid(fb80db71-e315-42f0-9ea9-dd3dd312ed70)] +interface nsICameraShutterCallback : nsISupports +{ + void handleEvent(); +}; + +[scriptable, function, uuid(a302c6c9-3776-4d1d-a395-f4105d47c3d3)] +interface nsICameraErrorCallback : nsISupports +{ + void handleEvent(in DOMString error); +}; + +/* + attributes here affect the preview, any pictures taken, and/or + any video recorded by the camera. +*/ +[scriptable, uuid(3066c884-d2c3-4477-847d-08ea1c2d188a)] +interface nsICameraControl : nsISupports +{ + readonly attribute nsICameraCapabilities capabilities; + + /* one of the vales chosen from capabilities.effects; + default is "none" */ + attribute DOMString effect; + + /* one of the values chosen from capabilities.whiteBalanceModes; + default is "auto" */ + attribute DOMString whiteBalanceMode; + + /* one of the valus chosen from capabilities.sceneModes; + default is "auto" */ + attribute DOMString sceneMode; + + /* one of the values chosen from capabilities.flashModes; + default is "auto" */ + attribute DOMString flashMode; + + /* one of the values chosen from capabilities.focusModes; + default is "auto", if supported, or "fixed" */ + attribute DOMString focusMode; + + /* one of the values chosen from capabilities.zoomRatios; other + values will be rounded to the nearest supported value; + default is 1.0 */ + attribute double zoom; + + /* an array of one or more objects that define where the + camera will perform light metering, each defining the properties: + { + top: -1000, + left: -1000, + bottom: 1000, + right: 1000, + weight: 1000 + } + + 'top', 'left', 'bottom', and 'right' all range from -1000 at + the top-/leftmost of the sensor to 1000 at the bottom-/rightmost + of the sensor. + + objects missing one or more of these properties will be ignored; + if the array contains more than capabilities.maxMeteringAreas, + extra areas will be ignored. + + this attribute can be set to null to allow the camera to determine + where to perform light metering. */ + [implicit_jscontext] + attribute jsval meteringAreas; + + /* an array of one or more objects that define where the camera will + perform auto-focusing, with the same definition as meteringAreas. + + if the array contains more than capabilities.maxFocusAreas, extra + areas will be ignored. + + this attribute can be set to null to allow the camera to determine + where to focus. */ + [implicit_jscontext] + attribute jsval focusAreas; + + /* focal length in millimetres */ + readonly attribute double focalLength; + + /* the distances in metres to where the image subject appears to be + in focus. 'focusDistanceOptimum' is where the subject will appear + sharpest; the difference between 'focusDistanceFar' and + 'focusDistanceNear' is the image's depth of field. + + 'focusDistanceFar' may be infinity. */ + readonly attribute double focusDistanceNear; + readonly attribute double focusDistanceOptimum; + readonly attribute double focusDistanceFar; + + /* 'compensation' is optional, and if missing, will + set the camera to use automatic exposure compensation. + + acceptable values must range from minExposureCompensation + to maxExposureCompensation in steps of stepExposureCompensation; + invalid values will be rounded to the nearest valid value. */ + [implicit_jscontext] + void setExposureCompensation([optional] in jsval compensation); + readonly attribute double exposureCompensation; + + /* the function to call on the camera's shutter event, to trigger + a shutter sound and/or a visual shutter indicator. */ + attribute nsICameraShutterCallback onShutter; + + /* tell the camera to attempt to focus the image */ + void autoFocus(in nsICameraAutoFocusCallback onSuccess, [optional] in nsICameraErrorCallback onError); + + /* capture an image and return it as a blob to the 'onSuccess' callback; + if the camera supports it, this may be invoked while the camera is + already recording video. + + invoking this function will stop the preview stream, which must be + manually restarted (e.g. by calling .play() on it). */ + [implicit_jscontext] + void takePicture(in jsval aOptions, in nsICameraTakePictureCallback onSuccess, [optional] in nsICameraErrorCallback onError); + + /* start recording video; 'aOptions' define the frame size of to + capture, chosen from capabilities.videoSizes, e.g.: + { + width: 640, + height: 480 + } + */ + [implicit_jscontext] + void startRecording(in jsval aOptions, in nsICameraStartRecordingCallback onSuccess, [optional] in nsICameraErrorCallback onError); + + /* stop precording video. */ + void stopRecording(); + + /* get a media stream to be used as a camera viewfinder; the options + define the desired frame size of the preview, chosen from + capabilities.previewSizes, e.g.: + { + height: 640, + width: 480, + } + */ + [implicit_jscontext] + void getPreviewStream(in jsval aOptions, in nsICameraPreviewStreamCallback onSuccess, [optional] in nsICameraErrorCallback onError); +}; + +[scriptable, function, uuid(a267afbc-d91c-413a-8de5-0b94aecffa3e)] +interface nsICameraGetCameraCallback : nsISupports +{ + void handleEvent(in nsICameraControl camera); +}; + +[scriptable, uuid(671ee624-0336-441a-a24e-26b5319f14fe)] +interface nsIDOMCameraManager : nsISupports +{ + /* get a camera instance; options will be used to specify which + camera to get from the list returned by getListOfCameras(), e.g.: + { + camera: front + } + */ + [implicit_jscontext] + void getCamera([optional] in jsval aOptions, in nsICameraGetCameraCallback onSuccess, [optional] in nsICameraErrorCallback onError); + + /* return a JSON array of camera identifiers, e.g. + [ "front", "back" ] + */ + [implicit_jscontext] + jsval getListOfCameras(); +}; diff --git a/dom/camera/nsIDOMNavigatorCamera.idl b/dom/camera/nsIDOMNavigatorCamera.idl new file mode 100644 index 00000000000..587eb506cf4 --- /dev/null +++ b/dom/camera/nsIDOMNavigatorCamera.idl @@ -0,0 +1,15 @@ +/* -*- Mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 40 -*- */ +/* vim: set ts=2 et sw=2 tw=40: */ +/* 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 "nsISupports.idl" + +interface nsIDOMCameraManager; + +[scriptable, uuid(bbb2456a-a6c8-42c8-8f52-6de071097e4b)] +interface nsIDOMNavigatorCamera : nsISupports +{ + readonly attribute nsIDOMCameraManager mozCameras; +}; diff --git a/dom/dom-config.mk b/dom/dom-config.mk index f91f51a1ea0..92b372f88b0 100644 --- a/dom/dom-config.mk +++ b/dom/dom-config.mk @@ -30,6 +30,7 @@ DOM_SRCDIRS = \ layout/style \ layout/xul/base/src \ layout/xul/base/src/tree/src \ + dom/camera \ $(NULL) ifdef MOZ_B2G_RIL diff --git a/dom/tests/mochitest/general/test_interfaces.html b/dom/tests/mochitest/general/test_interfaces.html index 63ae12aa6ea..b1f2fe626d6 100644 --- a/dom/tests/mochitest/general/test_interfaces.html +++ b/dom/tests/mochitest/general/test_interfaces.html @@ -523,7 +523,11 @@ var interfaceNamesInGlobalScope = "ContactTelephone", "ContactEmail", "SVGFitToViewBox", - "SVGAElement" + "SVGAElement", + "NavigatorCamera", + "CameraControl", + "CameraCapabilities", + "CameraManager" ] for (var i in Components.interfaces) { diff --git a/js/xpconnect/src/dictionary_helper_gen.conf b/js/xpconnect/src/dictionary_helper_gen.conf index 4061137b3e1..676320f6e10 100644 --- a/js/xpconnect/src/dictionary_helper_gen.conf +++ b/js/xpconnect/src/dictionary_helper_gen.conf @@ -18,7 +18,12 @@ dictionaries = [ [ 'GeoPositionOptions', 'nsIDOMGeoGeolocation.idl' ], [ 'DOMFileMetadataParameters', 'nsIDOMLockedFile.idl' ], [ 'XMLHttpRequestParameters', 'nsIXMLHttpRequest.idl' ], - [ 'DeviceStorageEnumerationParameters', 'nsIDOMDeviceStorage.idl' ] + [ 'DeviceStorageEnumerationParameters', 'nsIDOMDeviceStorage.idl' ], + [ 'CameraSize', 'nsIDOMCameraManager.idl' ], + [ 'CameraRegion', 'nsIDOMCameraManager.idl' ], + [ 'CameraPosition', 'nsIDOMCameraManager.idl' ], + [ 'CameraSelector', 'nsIDOMCameraManager.idl' ], + [ 'CameraPictureOptions', 'nsIDOMCameraManager.idl' ] ] # include file names diff --git a/layout/build/Makefile.in b/layout/build/Makefile.in index dff4e1193ef..71a50587b3b 100644 --- a/layout/build/Makefile.in +++ b/layout/build/Makefile.in @@ -121,6 +121,8 @@ ifdef MOZ_B2G_BT #{ SHARED_LIBRARY_LIBS += $(DEPTH)/dom/bluetooth/$(LIB_PREFIX)dombluetooth_s.$(LIB_SUFFIX) endif #} +SHARED_LIBRARY_LIBS += $(DEPTH)/dom/camera/$(LIB_PREFIX)domcamera_s.$(LIB_SUFFIX) + ifdef MOZ_B2G_RIL #{ SHARED_LIBRARY_LIBS += \ $(DEPTH)/dom/system/gonk/$(LIB_PREFIX)domsystemgonk_s.$(LIB_SUFFIX) \ @@ -273,4 +275,6 @@ ifdef MOZ_B2G_BT #{ LOCAL_INCLUDES += -I$(topsrcdir)/dom/bluetooth endif #} +LOCAL_INCLUDES += -I$(topsrcdir)/dom/camera + DEFINES += -D_IMPL_NS_LAYOUT diff --git a/mobile/xul/installer/package-manifest.in b/mobile/xul/installer/package-manifest.in index 5e5d9336c31..8cf2a0a8ed8 100644 --- a/mobile/xul/installer/package-manifest.in +++ b/mobile/xul/installer/package-manifest.in @@ -164,6 +164,7 @@ #ifdef MOZ_B2G_BT @BINPATH@/components/dom_bluetooth.xpt #endif +@BINPATH@/components/dom_camera.xpt @BINPATH@/components/dom_canvas.xpt @BINPATH@/components/dom_core.xpt @BINPATH@/components/dom_css.xpt From c37e1016cb7f727f5bb87942fab727c3eaa54375 Mon Sep 17 00:00:00 2001 From: Nicholas Cameron Date: Tue, 31 Jul 2012 17:18:43 +1200 Subject: [PATCH 14/18] backout bug 773460 --- modules/libpref/src/init/all.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/modules/libpref/src/init/all.js b/modules/libpref/src/init/all.js index b79c5f39343..3f349ced999 100644 --- a/modules/libpref/src/init/all.js +++ b/modules/libpref/src/init/all.js @@ -226,10 +226,10 @@ pref("gfx.font_rendering.directwrite.use_gdi_table_loading", true); #endif #ifdef XP_WIN +pref("gfx.canvas.azure.enabled", true); // comma separated list of backends to use in order of preference // e.g., pref("gfx.canvas.azure.backends", "direct2d,skia,cairo"); -pref("gfx.canvas.azure.enabled", true); -pref("gfx.canvas.azure.backends", "direct2d,cairo"); +pref("gfx.canvas.azure.backends", "direct2d"); pref("gfx.content.azure.enabled", true); #else #ifdef XP_MACOSX @@ -237,7 +237,7 @@ pref("gfx.canvas.azure.enabled", true); pref("gfx.canvas.azure.backends", "cg"); #else pref("gfx.canvas.azure.enabled", false); -pref("gfx.canvas.azure.backends", "cairo"); +pref("gfx.canvas.azure.backends", "cairo,skia"); #endif #endif From 9d7b627cd228e31163f62239fd253fe133adcf33 Mon Sep 17 00:00:00 2001 From: Michael Wu Date: Tue, 31 Jul 2012 01:45:26 -0400 Subject: [PATCH 15/18] Follow up to Bug 740997 - land the right version of GonkCameraCapabilities.cpp, r=killer --- dom/camera/GonkCameraCapabilities.cpp | 40 +++++++++++++-------------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/dom/camera/GonkCameraCapabilities.cpp b/dom/camera/GonkCameraCapabilities.cpp index b2420ea6d23..e905f476f9b 100644 --- a/dom/camera/GonkCameraCapabilities.cpp +++ b/dom/camera/GonkCameraCapabilities.cpp @@ -119,11 +119,11 @@ ParseDimensionItemAndAdd(JSContext* aCx, JSObject* aArray, PRUint32 aIndex, cons } nsresult -nsCameraCapabilities::ParameterListToNewArray(JSContext* aCx, JSObject** aArray, const char* aKey, ParseItemAndAddFunc aParseItemAndAdd) +nsCameraCapabilities::ParameterListToNewArray(JSContext* aCx, JSObject** aArray, PRUint32 aKey, ParseItemAndAddFunc aParseItemAndAdd) { NS_ENSURE_TRUE(mCamera, NS_ERROR_NOT_AVAILABLE); - const char* value = mCamera->GetParameter(aKey); + const char* value = mCamera->GetParameterConstChar(aKey); if (!value) { // in case we get nonsense data back *aArray = nullptr; @@ -157,7 +157,7 @@ nsCameraCapabilities::ParameterListToNewArray(JSContext* aCx, JSObject** aArray, } nsresult -nsCameraCapabilities::StringListToNewObject(JSContext* aCx, JS::Value* aArray, const char* aKey) +nsCameraCapabilities::StringListToNewObject(JSContext* aCx, JS::Value* aArray, PRUint32 aKey) { JSObject* array; @@ -169,7 +169,7 @@ nsCameraCapabilities::StringListToNewObject(JSContext* aCx, JS::Value* aArray, c } nsresult -nsCameraCapabilities::DimensionListToNewObject(JSContext* aCx, JS::Value* aArray, const char* aKey) +nsCameraCapabilities::DimensionListToNewObject(JSContext* aCx, JS::Value* aArray, PRUint32 aKey) { JSObject* array; nsresult rv; @@ -185,56 +185,56 @@ nsCameraCapabilities::DimensionListToNewObject(JSContext* aCx, JS::Value* aArray NS_IMETHODIMP nsCameraCapabilities::GetPreviewSizes(JSContext* cx, JS::Value* aPreviewSizes) { - return DimensionListToNewObject(cx, aPreviewSizes, CameraParameters::KEY_SUPPORTED_PREVIEW_SIZES); + return DimensionListToNewObject(cx, aPreviewSizes, nsCameraControl::CAMERA_PARAM_SUPPORTED_PREVIEWSIZES); } /* readonly attribute jsval pictureSizes; */ NS_IMETHODIMP nsCameraCapabilities::GetPictureSizes(JSContext* cx, JS::Value* aPictureSizes) { - return DimensionListToNewObject(cx, aPictureSizes, CameraParameters::KEY_SUPPORTED_PICTURE_SIZES); + return DimensionListToNewObject(cx, aPictureSizes, nsCameraControl::CAMERA_PARAM_SUPPORTED_PICTURESIZES); } /* readonly attribute jsval fileFormats; */ NS_IMETHODIMP nsCameraCapabilities::GetFileFormats(JSContext* cx, JS::Value* aFileFormats) { - return StringListToNewObject(cx, aFileFormats, CameraParameters::KEY_SUPPORTED_PICTURE_FORMATS); + return StringListToNewObject(cx, aFileFormats, nsCameraControl::CAMERA_PARAM_SUPPORTED_PICTUREFORMATS); } /* readonly attribute jsval whiteBalanceModes; */ NS_IMETHODIMP nsCameraCapabilities::GetWhiteBalanceModes(JSContext* cx, JS::Value* aWhiteBalanceModes) { - return StringListToNewObject(cx, aWhiteBalanceModes, CameraParameters::KEY_SUPPORTED_WHITE_BALANCE); + return StringListToNewObject(cx, aWhiteBalanceModes, nsCameraControl::CAMERA_PARAM_SUPPORTED_WHITEBALANCES); } /* readonly attribute jsval sceneModes; */ NS_IMETHODIMP nsCameraCapabilities::GetSceneModes(JSContext* cx, JS::Value* aSceneModes) { - return StringListToNewObject(cx, aSceneModes, CameraParameters::KEY_SUPPORTED_SCENE_MODES); + return StringListToNewObject(cx, aSceneModes, nsCameraControl::CAMERA_PARAM_SUPPORTED_SCENEMODES); } /* readonly attribute jsval effects; */ NS_IMETHODIMP nsCameraCapabilities::GetEffects(JSContext* cx, JS::Value* aEffects) { - return StringListToNewObject(cx, aEffects, CameraParameters::KEY_SUPPORTED_EFFECTS); + return StringListToNewObject(cx, aEffects, nsCameraControl::CAMERA_PARAM_SUPPORTED_EFFECTS); } /* readonly attribute jsval flashModes; */ NS_IMETHODIMP nsCameraCapabilities::GetFlashModes(JSContext* cx, JS::Value* aFlashModes) { - return StringListToNewObject(cx, aFlashModes, CameraParameters::KEY_SUPPORTED_FLASH_MODES); + return StringListToNewObject(cx, aFlashModes, nsCameraControl::CAMERA_PARAM_SUPPORTED_FLASHMODES); } /* readonly attribute jsval focusModes; */ NS_IMETHODIMP nsCameraCapabilities::GetFocusModes(JSContext* cx, JS::Value* aFocusModes) { - return StringListToNewObject(cx, aFocusModes, CameraParameters::KEY_SUPPORTED_FOCUS_MODES); + return StringListToNewObject(cx, aFocusModes, nsCameraControl::CAMERA_PARAM_SUPPORTED_FOCUSMODES); } /* readonly attribute long maxFocusAreas; */ @@ -243,7 +243,7 @@ nsCameraCapabilities::GetMaxFocusAreas(JSContext* cx, PRInt32* aMaxFocusAreas) { NS_ENSURE_TRUE(mCamera, NS_ERROR_NOT_AVAILABLE); - const char* value = mCamera->GetParameter(CameraParameters::KEY_MAX_NUM_FOCUS_AREAS); + const char* value = mCamera->GetParameterConstChar(nsCameraControl::CAMERA_PARAM_SUPPORTED_MAXFOCUSAREAS); if (!value) { // in case we get nonsense data back *aMaxFocusAreas = 0; @@ -260,7 +260,7 @@ nsCameraCapabilities::GetMinExposureCompensation(JSContext* cx, double* aMinExpo { NS_ENSURE_TRUE(mCamera, NS_ERROR_NOT_AVAILABLE); - const char* value = mCamera->GetParameter(CameraParameters::KEY_MIN_EXPOSURE_COMPENSATION); + const char* value = mCamera->GetParameterConstChar(nsCameraControl::CAMERA_PARAM_SUPPORTED_MINEXPOSURECOMPENSATION); if (!value) { // in case we get nonsense data back *aMinExposureCompensation = 0; @@ -277,7 +277,7 @@ nsCameraCapabilities::GetMaxExposureCompensation(JSContext* cx, double* aMaxExpo { NS_ENSURE_TRUE(mCamera, NS_ERROR_NOT_AVAILABLE); - const char* value = mCamera->GetParameter(CameraParameters::KEY_MAX_EXPOSURE_COMPENSATION); + const char* value = mCamera->GetParameterConstChar(nsCameraControl::CAMERA_PARAM_SUPPORTED_MAXEXPOSURECOMPENSATION); if (!value) { // in case we get nonsense data back *aMaxExposureCompensation = 0; @@ -294,7 +294,7 @@ nsCameraCapabilities::GetStepExposureCompensation(JSContext* cx, double* aStepEx { NS_ENSURE_TRUE(mCamera, NS_ERROR_NOT_AVAILABLE); - const char* value = mCamera->GetParameter(CameraParameters::KEY_EXPOSURE_COMPENSATION_STEP); + const char* value = mCamera->GetParameterConstChar(nsCameraControl::CAMERA_PARAM_SUPPORTED_EXPOSURECOMPENSATIONSTEP); if (!value) { // in case we get nonsense data back *aStepExposureCompensation = 0; @@ -311,7 +311,7 @@ nsCameraCapabilities::GetMaxMeteringAreas(JSContext* cx, PRInt32* aMaxMeteringAr { NS_ENSURE_TRUE(mCamera, NS_ERROR_NOT_AVAILABLE); - const char* value = mCamera->GetParameter(CameraParameters::KEY_MAX_NUM_METERING_AREAS); + const char* value = mCamera->GetParameterConstChar(nsCameraControl::CAMERA_PARAM_SUPPORTED_MAXMETERINGAREAS); if (!value) { // in case we get nonsense data back *aMaxMeteringAreas = 0; @@ -328,7 +328,7 @@ nsCameraCapabilities::GetZoomRatios(JSContext* cx, JS::Value* aZoomRatios) { NS_ENSURE_TRUE(mCamera, NS_ERROR_NOT_AVAILABLE); - const char* value = mCamera->GetParameter(CameraParameters::KEY_ZOOM_SUPPORTED); + const char* value = mCamera->GetParameterConstChar(nsCameraControl::CAMERA_PARAM_SUPPORTED_ZOOM); if (!value || strcmp(value, CameraParameters::TRUE) != 0) { // if zoom is not supported, return a null object *aZoomRatios = JSVAL_NULL; @@ -337,7 +337,7 @@ nsCameraCapabilities::GetZoomRatios(JSContext* cx, JS::Value* aZoomRatios) JSObject* array; - nsresult rv = ParameterListToNewArray(cx, &array, CameraParameters::KEY_ZOOM_RATIOS, ParseZoomRatioItemAndAdd); + nsresult rv = ParameterListToNewArray(cx, &array, nsCameraControl::CAMERA_PARAM_SUPPORTED_ZOOMRATIOS, ParseZoomRatioItemAndAdd); NS_ENSURE_SUCCESS(rv, rv); *aZoomRatios = OBJECT_TO_JSVAL(array); @@ -348,5 +348,5 @@ nsCameraCapabilities::GetZoomRatios(JSContext* cx, JS::Value* aZoomRatios) NS_IMETHODIMP nsCameraCapabilities::GetVideoSizes(JSContext* cx, JS::Value* aVideoSizes) { - return DimensionListToNewObject(cx, aVideoSizes, CameraParameters::KEY_SUPPORTED_VIDEO_SIZES); + return DimensionListToNewObject(cx, aVideoSizes, nsCameraControl::CAMERA_PARAM_SUPPORTED_VIDEOSIZES); } From eacaffca322ccafa06fc567d9072420e5abc62f0 Mon Sep 17 00:00:00 2001 From: Justin Dolske Date: Mon, 30 Jul 2012 22:54:56 -0700 Subject: [PATCH 16/18] Bug 778855 - Fix Talos Ts regression by returning to late Safe Browsing initialization. r=gavin --- browser/base/content/browser.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/browser/base/content/browser.js b/browser/base/content/browser.js index d474951b0d5..53240b53e00 100644 --- a/browser/base/content/browser.js +++ b/browser/base/content/browser.js @@ -1245,7 +1245,8 @@ var gBrowserInit = { gDelayedStartupTimeoutId = null; #ifdef MOZ_SAFE_BROWSING - SafeBrowsing.init(); + // Bug 778855 - Perf regression if we do this here. To be addressed in bug 779008. + setTimeout(function() { SafeBrowsing.init(); }, 2000); #endif Services.obs.addObserver(gSessionHistoryObserver, "browser:purge-session-history", false); From 17ca9e27e71f7af61089372f7a1616f4a8d5c88c Mon Sep 17 00:00:00 2001 From: Nicholas Cameron Date: Tue, 31 Jul 2012 17:58:04 +1200 Subject: [PATCH 17/18] bug 746883, restore an accidently removed fails-if. r=karlt --HG-- extra : rebase_source : 787ed1feadfbbf2c04d640fa3fbc2ca859d104e5 --- layout/reftests/css-gradients/reftest.list | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/layout/reftests/css-gradients/reftest.list b/layout/reftests/css-gradients/reftest.list index f6e7b6a40b0..75788cd2242 100644 --- a/layout/reftests/css-gradients/reftest.list +++ b/layout/reftests/css-gradients/reftest.list @@ -1,5 +1,5 @@ fuzzy-if(!contentSameGfxBackendAsCanvas,2,88500) fails-if(Android) == linear-1a.html linear-1-ref.html -fuzzy-if(!contentSameGfxBackendAsCanvas,2,88500) == linear-1b.html linear-1-ref.html +fuzzy-if(!contentSameGfxBackendAsCanvas,2,88500) fails-if(Android) == linear-1b.html linear-1-ref.html fuzzy-if(!contentSameGfxBackendAsCanvas,2,88500) fails-if(Android) == linear-keywords-1a.html linear-keywords-1-ref.html fuzzy-if(!contentSameGfxBackendAsCanvas,2,88500) fails-if(Android) == linear-keywords-1b.html linear-keywords-1-ref.html fuzzy-if(!contentSameGfxBackendAsCanvas,2,88500) fails-if(Android) == linear-percent.html linear-percent-ref.html From dc2316da1008ee123934f2dd79cdb8c37a2ed243 Mon Sep 17 00:00:00 2001 From: Nicholas Cameron Date: Tue, 31 Jul 2012 18:14:45 +1200 Subject: [PATCH 18/18] Backed out changeset af118f5f9444 --- layout/reftests/css-gradients/reftest.list | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/layout/reftests/css-gradients/reftest.list b/layout/reftests/css-gradients/reftest.list index 75788cd2242..f6e7b6a40b0 100644 --- a/layout/reftests/css-gradients/reftest.list +++ b/layout/reftests/css-gradients/reftest.list @@ -1,5 +1,5 @@ fuzzy-if(!contentSameGfxBackendAsCanvas,2,88500) fails-if(Android) == linear-1a.html linear-1-ref.html -fuzzy-if(!contentSameGfxBackendAsCanvas,2,88500) fails-if(Android) == linear-1b.html linear-1-ref.html +fuzzy-if(!contentSameGfxBackendAsCanvas,2,88500) == linear-1b.html linear-1-ref.html fuzzy-if(!contentSameGfxBackendAsCanvas,2,88500) fails-if(Android) == linear-keywords-1a.html linear-keywords-1-ref.html fuzzy-if(!contentSameGfxBackendAsCanvas,2,88500) fails-if(Android) == linear-keywords-1b.html linear-keywords-1-ref.html fuzzy-if(!contentSameGfxBackendAsCanvas,2,88500) fails-if(Android) == linear-percent.html linear-percent-ref.html