Merge mozilla-central to fx-team

This commit is contained in:
Carsten "Tomcat" Book 2014-12-18 13:58:10 +01:00
commit 5c8fbd319a
185 changed files with 2275 additions and 1141 deletions

View File

@ -51,3 +51,5 @@ if CONFIG['MOZ_ENABLE_DBUS']:
CXXFLAGS += CONFIG['MOZ_DBUS_CFLAGS']
include('/ipc/chromium/chromium-config.mozbuild')
FAIL_ON_WARNINGS = True

View File

@ -29,3 +29,5 @@ if CONFIG['ACCESSIBILITY']:
FINAL_LIBRARY = 'xul'
include('/ipc/chromium/chromium-config.mozbuild')
FAIL_ON_WARNINGS = True

View File

@ -22,3 +22,5 @@ LOCAL_INCLUDES += [
]
FINAL_LIBRARY = 'xul'
FAIL_ON_WARNINGS = True

View File

@ -50,3 +50,5 @@ FINAL_LIBRARY = 'xul'
# macros which conflicts with std::min/max. Suppress the macros:
if CONFIG['OS_ARCH'] == 'WINNT':
DEFINES['NOMINMAX'] = True
FAIL_ON_WARNINGS = True

View File

@ -59,3 +59,5 @@ LOCAL_INCLUDES += [
include('/ipc/chromium/chromium-config.mozbuild')
FINAL_LIBRARY = 'xul'
FAIL_ON_WARNINGS = True

View File

@ -24,3 +24,5 @@ LOCAL_INCLUDES += [
DEFINES['NOMINMAX'] = True
FINAL_LIBRARY = 'xul'
FAIL_ON_WARNINGS = True

View File

@ -23,3 +23,5 @@ FINAL_LIBRARY = 'xul'
# macros which conflicts with std::min/max. Suppress the macros:
if CONFIG['OS_ARCH'] == 'WINNT':
DEFINES['NOMINMAX'] = True
FAIL_ON_WARNINGS = True

View File

@ -47,3 +47,6 @@ else:
]
FINAL_LIBRARY = 'xul'
if CONFIG['GNU_CXX']:
FAIL_ON_WARNINGS = True

View File

@ -1073,3 +1073,8 @@ pref("dom.mozSettings.SettingsDB.verbose.enabled", false);
pref("dom.mozSettings.SettingsManager.verbose.enabled", false);
pref("dom.mozSettings.SettingsRequestManager.verbose.enabled", false);
pref("dom.mozSettings.SettingsService.verbose.enabled", false);
// Controlling whether we want to allow forcing some Settings
// IndexedDB transactions to be opened as readonly or keep everything as
// readwrite.
pref("dom.mozSettings.allowForceReadOnly", false);

View File

@ -74,3 +74,5 @@ if CONFIG['OS_ARCH'] == 'WINNT':
OS_LIBS += [
'version',
]
FAIL_ON_WARNINGS = True

View File

@ -15,7 +15,7 @@
<project name="platform_build" path="build" remote="b2g" revision="3ab0d9c70f0b2e1ededc679112c392303f037361">
<copyfile dest="Makefile" src="core/root.mk"/>
</project>
<project name="gaia" path="gaia" remote="mozillaorg" revision="7a865bd6163e9a0372861d27450b3434875ef5c1"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="58734e8a48157f99d5b733412b600c2e04c954fe"/>
<project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="50ad16a280fe9cfa0716f8c6ba16afdf7f266b49"/>
<project name="librecovery" path="librecovery" remote="b2g" revision="891e5069c0ad330d8191bf8c7b879c814258c89f"/>

View File

@ -19,7 +19,7 @@
<copyfile dest="Makefile" src="core/root.mk"/>
</project>
<project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/>
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="7a865bd6163e9a0372861d27450b3434875ef5c1"/>
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="58734e8a48157f99d5b733412b600c2e04c954fe"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="50ad16a280fe9cfa0716f8c6ba16afdf7f266b49"/>
<project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
<project name="platform_hardware_ril" path="hardware/ril" remote="b2g" revision="d5d3f93914558b6f168447b805cd799c8233e300"/>

View File

@ -17,7 +17,7 @@
</project>
<project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
<project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="7a865bd6163e9a0372861d27450b3434875ef5c1"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="58734e8a48157f99d5b733412b600c2e04c954fe"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="50ad16a280fe9cfa0716f8c6ba16afdf7f266b49"/>
<project name="moztt" path="external/moztt" remote="b2g" revision="fe893bb760a3bb64375f62fdf4762a58c59df9ef"/>
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="f0e6d8bde961683b7862b4eb0bb04c49d9699f3c"/>

View File

@ -15,7 +15,7 @@
<project name="platform_build" path="build" remote="b2g" revision="3ab0d9c70f0b2e1ededc679112c392303f037361">
<copyfile dest="Makefile" src="core/root.mk"/>
</project>
<project name="gaia" path="gaia" remote="mozillaorg" revision="7a865bd6163e9a0372861d27450b3434875ef5c1"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="58734e8a48157f99d5b733412b600c2e04c954fe"/>
<project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="50ad16a280fe9cfa0716f8c6ba16afdf7f266b49"/>
<project name="librecovery" path="librecovery" remote="b2g" revision="891e5069c0ad330d8191bf8c7b879c814258c89f"/>

View File

@ -19,7 +19,7 @@
<copyfile dest="Makefile" src="core/root.mk"/>
</project>
<project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/>
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="7a865bd6163e9a0372861d27450b3434875ef5c1"/>
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="58734e8a48157f99d5b733412b600c2e04c954fe"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="50ad16a280fe9cfa0716f8c6ba16afdf7f266b49"/>
<project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
<project name="platform_hardware_ril" path="hardware/ril" remote="b2g" revision="d5d3f93914558b6f168447b805cd799c8233e300"/>

View File

@ -15,7 +15,7 @@
<project name="platform_build" path="build" remote="b2g" revision="3ab0d9c70f0b2e1ededc679112c392303f037361">
<copyfile dest="Makefile" src="core/root.mk"/>
</project>
<project name="gaia" path="gaia" remote="mozillaorg" revision="7a865bd6163e9a0372861d27450b3434875ef5c1"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="58734e8a48157f99d5b733412b600c2e04c954fe"/>
<project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="50ad16a280fe9cfa0716f8c6ba16afdf7f266b49"/>
<project name="librecovery" path="librecovery" remote="b2g" revision="891e5069c0ad330d8191bf8c7b879c814258c89f"/>

View File

@ -17,7 +17,7 @@
</project>
<project name="librecovery" path="librecovery" remote="b2g" revision="891e5069c0ad330d8191bf8c7b879c814258c89f"/>
<project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="7a865bd6163e9a0372861d27450b3434875ef5c1"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="58734e8a48157f99d5b733412b600c2e04c954fe"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="50ad16a280fe9cfa0716f8c6ba16afdf7f266b49"/>
<project name="moztt" path="external/moztt" remote="b2g" revision="fe893bb760a3bb64375f62fdf4762a58c59df9ef"/>
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="f0e6d8bde961683b7862b4eb0bb04c49d9699f3c"/>

View File

@ -4,6 +4,6 @@
"remote": "",
"branch": ""
},
"revision": "fbcff6610f8b99726ef14b0c6d894b4a7053eecf",
"revision": "d937413f736efd995436c211649f930191429b66",
"repo_path": "integration/gaia-central"
}

View File

@ -17,7 +17,7 @@
<copyfile dest="Makefile" src="core/root.mk"/>
</project>
<project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/>
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="7a865bd6163e9a0372861d27450b3434875ef5c1"/>
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="58734e8a48157f99d5b733412b600c2e04c954fe"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="50ad16a280fe9cfa0716f8c6ba16afdf7f266b49"/>
<project name="librecovery" path="librecovery" remote="b2g" revision="891e5069c0ad330d8191bf8c7b879c814258c89f"/>
<project name="moztt" path="external/moztt" remote="b2g" revision="fe893bb760a3bb64375f62fdf4762a58c59df9ef"/>

View File

@ -15,7 +15,7 @@
<copyfile dest="Makefile" src="core/root.mk"/>
</project>
<project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/>
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="7a865bd6163e9a0372861d27450b3434875ef5c1"/>
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="58734e8a48157f99d5b733412b600c2e04c954fe"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="50ad16a280fe9cfa0716f8c6ba16afdf7f266b49"/>
<project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
<project name="librecovery" path="librecovery" remote="b2g" revision="891e5069c0ad330d8191bf8c7b879c814258c89f"/>

View File

@ -17,7 +17,7 @@
</project>
<project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
<project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="7a865bd6163e9a0372861d27450b3434875ef5c1"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="58734e8a48157f99d5b733412b600c2e04c954fe"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="50ad16a280fe9cfa0716f8c6ba16afdf7f266b49"/>
<project name="moztt" path="external/moztt" remote="b2g" revision="fe893bb760a3bb64375f62fdf4762a58c59df9ef"/>
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="f0e6d8bde961683b7862b4eb0bb04c49d9699f3c"/>

View File

@ -17,7 +17,7 @@
<copyfile dest="Makefile" src="core/root.mk"/>
</project>
<project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/>
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="7a865bd6163e9a0372861d27450b3434875ef5c1"/>
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="58734e8a48157f99d5b733412b600c2e04c954fe"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="50ad16a280fe9cfa0716f8c6ba16afdf7f266b49"/>
<project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
<project name="librecovery" path="librecovery" remote="b2g" revision="891e5069c0ad330d8191bf8c7b879c814258c89f"/>

View File

@ -18,3 +18,5 @@ else:
]
DEFINES['B2G_NAME'] = '"%s-bin%s"' % (PROGRAM, CONFIG['BIN_SUFFIX'])
DEFINES['GAIA_PATH'] = '"gaia/profile"'
FAIL_ON_WARNINGS = True

View File

@ -72,3 +72,5 @@ if CONFIG['HAVE_CLOCK_MONOTONIC']:
OS_LIBS += CONFIG['REALTIME_LIBS']
JAR_MANIFESTS += ['jar.mn']
FAIL_ON_WARNINGS = True

View File

@ -17,3 +17,5 @@ FINAL_LIBRARY = 'browsercomps'
LOCAL_INCLUDES += [
'../build',
]
FAIL_ON_WARNINGS = True

View File

@ -34,3 +34,5 @@ if CONFIG['OS_ARCH'] == 'WINNT':
# GTK2: Need to link with glib for GNOME shell service
if CONFIG['MOZ_WIDGET_TOOLKIT'] in ('cocoa', 'gtk2', 'gtk3'):
OS_LIBS += CONFIG['TK_LIBS']
FAIL_ON_WARNINGS = True

View File

@ -21,3 +21,5 @@ FINAL_LIBRARY = 'browsercomps'
LOCAL_INCLUDES += [
'../build'
]
FAIL_ON_WARNINGS = True

View File

@ -39,3 +39,5 @@ for var in ('MOZ_APP_NAME', 'MOZ_MACBUNDLE_NAME'):
LOCAL_INCLUDES += [
'../build',
]
FAIL_ON_WARNINGS = True

View File

@ -52,3 +52,5 @@ EXTRA_PP_JS_MODULES += [
]
FINAL_LIBRARY = 'browsercomps'
FAIL_ON_WARNINGS = True

View File

@ -49,3 +49,5 @@ for var in ('MOZ_APP_NAME', 'MOZ_APP_VERSION'):
DEFINES[var] = '"%s"' % CONFIG[var]
CXXFLAGS += CONFIG['TK_CFLAGS']
FAIL_ON_WARNINGS = True

View File

@ -3856,7 +3856,6 @@ if test -n "$MOZ_FMP4"; then
else
MOZ_FMP4=
fi
MOZ_EME=1
MOZ_FFMPEG=
MOZ_WEBRTC=1
MOZ_PEERCONNECTION=
@ -5327,6 +5326,7 @@ MOZ_ARG_DISABLE_BOOL(fmp4,
if test -n "$MOZ_FMP4"; then
AC_DEFINE(MOZ_FMP4)
MOZ_EME=1
fi;
dnl ========================================================
@ -5339,6 +5339,9 @@ MOZ_ARG_DISABLE_BOOL(eme,
MOZ_EME=1)
if test -n "$MOZ_EME"; then
if test -z "$MOZ_FMP4"; then
AC_MSG_ERROR([Encrypted Media Extension support requires Fragmented MP4 support])
fi
AC_DEFINE(MOZ_EME)
fi;

View File

@ -24,3 +24,5 @@ if CONFIG["MOZ_WIDGET_TOOLKIT"] == "cocoa":
LOCAL_INCLUDES += ['/uriloader/exthandler/mac']
FINAL_LIBRARY = 'xul'
FAIL_ON_WARNINGS = True

View File

@ -11,14 +11,19 @@
#include "nsIDocument.h" // For nsIDocument
#include "nsIPresShell.h" // For nsIPresShell
#include "nsLayoutUtils.h" // For PostRestyleEvent (remove after bug 1073336)
#include "PendingPlayerTracker.h" // For PendingPlayerTracker
namespace mozilla {
namespace dom {
NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(AnimationPlayer, mTimeline, mSource)
NS_IMPL_CYCLE_COLLECTION_ROOT_NATIVE(AnimationPlayer, AddRef)
NS_IMPL_CYCLE_COLLECTION_UNROOT_NATIVE(AnimationPlayer, Release)
NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(AnimationPlayer, mTimeline,
mSource, mReady)
NS_IMPL_CYCLE_COLLECTING_ADDREF(AnimationPlayer)
NS_IMPL_CYCLE_COLLECTING_RELEASE(AnimationPlayer)
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(AnimationPlayer)
NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
NS_INTERFACE_MAP_ENTRY(nsISupports)
NS_INTERFACE_MAP_END
JSObject*
AnimationPlayer::WrapObject(JSContext* aCx)
@ -47,6 +52,10 @@ AnimationPlayer::GetCurrentTime() const
AnimationPlayState
AnimationPlayer::PlayState() const
{
if (mIsPending) {
return AnimationPlayState::Pending;
}
Nullable<TimeDuration> currentTime = GetCurrentTime();
if (currentTime.IsNull()) {
return AnimationPlayState::Idle;
@ -63,6 +72,26 @@ AnimationPlayer::PlayState() const
return AnimationPlayState::Running;
}
Promise*
AnimationPlayer::GetReady(ErrorResult& aRv)
{
// Lazily create the ready promise if it doesn't exist
if (!mReady) {
nsIGlobalObject* global = mTimeline->GetParentObject();
if (global) {
mReady = Promise::Create(global, aRv);
if (mReady && PlayState() != AnimationPlayState::Pending) {
mReady->MaybeResolve(this);
}
}
}
if (!mReady) {
aRv.Throw(NS_ERROR_FAILURE);
}
return mReady;
}
void
AnimationPlayer::Play()
{
@ -110,7 +139,7 @@ AnimationPlayer::Tick()
}
void
AnimationPlayer::ResolveStartTime()
AnimationPlayer::StartNow()
{
// Currently we only expect this method to be called when we are in the
// middle of initiating/resuming playback so we should have an unresolved
@ -125,6 +154,24 @@ AnimationPlayer::ResolveStartTime()
MOZ_ASSERT(!readyTime.IsNull(), "Missing or inactive timeline");
mStartTime.SetValue(readyTime.Value() - mHoldTime.Value());
mHoldTime.SetNull();
if (mReady) {
mReady->MaybeResolve(this);
}
}
void
AnimationPlayer::Cancel()
{
if (mIsPending) {
CancelPendingPlay();
if (mReady) {
mReady->MaybeReject(NS_ERROR_DOM_ABORT_ERR);
}
}
mHoldTime.SetNull();
mStartTime.SetNull();
}
bool
@ -173,7 +220,8 @@ AnimationPlayer::ComposeStyle(nsRefPtr<css::AnimValuesStyleRule>& aStyleRule,
}
AnimationPlayState playState = PlayState();
if (playState == AnimationPlayState::Running) {
if (playState == AnimationPlayState::Running ||
playState == AnimationPlayState::Pending) {
aNeedsRefreshes = true;
}
@ -197,21 +245,32 @@ AnimationPlayer::DoPlay()
return;
}
ResolveStartTime();
// Clear ready promise. We'll create a new one lazily.
mReady = nullptr;
StartNow();
}
void
AnimationPlayer::DoPause()
{
if (IsPaused()) {
return;
if (mIsPending) {
CancelPendingPlay();
// Resolve the ready promise since we currently only use it for
// players that are waiting to play. Later (in bug 1109390), we will
// use this for players waiting to pause as well and then we won't
// want to resolve it just yet.
if (mReady) {
mReady->MaybeResolve(this);
}
}
// Mark this as no longer running on the compositor so that next time
// we update animations we won't throttle them and will have a chance
// to remove the animation from any layer it might be on.
mIsRunningOnCompositor = false;
// Bug 927349 - check for null result here and go to pending state
// Bug 1109390 - check for null result here and go to pending state
mHoldTime = GetCurrentTime();
mStartTime.SetNull();
}
@ -234,6 +293,24 @@ AnimationPlayer::PostUpdate()
}
}
void
AnimationPlayer::CancelPendingPlay()
{
if (!mIsPending) {
return;
}
nsIDocument* doc = GetRenderedDocument();
if (doc) {
PendingPlayerTracker* tracker = doc->GetPendingPlayerTracker();
if (tracker) {
tracker->RemovePlayPending(*this);
}
}
mIsPending = false;
}
StickyTimeDuration
AnimationPlayer::SourceContentEnd() const
{

View File

@ -13,6 +13,7 @@
#include "mozilla/dom/Animation.h" // for Animation
#include "mozilla/dom/AnimationPlayerBinding.h" // for AnimationPlayState
#include "mozilla/dom/AnimationTimeline.h" // for AnimationTimeline
#include "mozilla/dom/Promise.h" // for Promise
#include "nsCSSProperty.h" // for nsCSSProperty
// X11 has a #define for CurrentTime.
@ -37,7 +38,8 @@ class CSSTransitionPlayer;
namespace dom {
class AnimationPlayer : public nsWrapperCache
class AnimationPlayer : public nsISupports,
public nsWrapperCache
{
protected:
virtual ~AnimationPlayer() { }
@ -45,13 +47,14 @@ protected:
public:
explicit AnimationPlayer(AnimationTimeline* aTimeline)
: mTimeline(aTimeline)
, mIsPending(false)
, mIsRunningOnCompositor(false)
, mIsPreviousStateFinished(false)
{
}
NS_INLINE_DECL_CYCLE_COLLECTING_NATIVE_REFCOUNTING(AnimationPlayer)
NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_NATIVE_CLASS(AnimationPlayer)
NS_DECL_CYCLE_COLLECTING_ISUPPORTS
NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(AnimationPlayer)
AnimationTimeline* GetParentObject() const { return mTimeline; }
virtual JSObject* WrapObject(JSContext* aCx) MOZ_OVERRIDE;
@ -65,6 +68,7 @@ public:
Nullable<TimeDuration> GetStartTime() const { return mStartTime; }
Nullable<TimeDuration> GetCurrentTime() const;
AnimationPlayState PlayState() const;
virtual Promise* GetReady(ErrorResult& aRv);
virtual void Play();
virtual void Pause();
bool IsRunningOnCompositor() const { return mIsRunningOnCompositor; }
@ -84,10 +88,11 @@ public:
void SetSource(Animation* aSource);
void Tick();
// Sets the start time of the player to the current time of its timeline.
// This should only be called on a player that is currently waiting to play
// (and therefore has a null start time but a fixed hold time).
void ResolveStartTime();
// Sets the start time of a player that is waiting to play to the current
// time of its timeline.
void StartNow();
void Cancel();
const nsString& Name() const {
return mSource ? mSource->Name() : EmptyString();
@ -128,6 +133,10 @@ protected:
void FlushStyle() const;
void PostUpdate();
// Remove this player from the pending player tracker and resets mIsPending
// as necessary. The caller is responsible for resolving or aborting the
// mReady promise as necessary.
void CancelPendingPlay();
StickyTimeDuration SourceContentEnd() const;
nsIDocument* GetRenderedDocument() const;
@ -140,6 +149,18 @@ protected:
// The beginning of the delay period.
Nullable<TimeDuration> mStartTime; // Timeline timescale
Nullable<TimeDuration> mHoldTime; // Player timescale
// A Promise that is replaced on each call to Play() (and in future Pause())
// and fulfilled when Play() is successfully completed.
// This object is lazily created by GetReady.
nsRefPtr<Promise> mReady;
// Indicates if the player is in the pending state. We use this rather
// than checking if this player is tracked by a PendingPlayerTracker.
// This is because the PendingPlayerTracker is associated with the source
// content's document but we need to know if we're pending even if the
// source content loses association with its document.
bool mIsPending;
bool mIsRunningOnCompositor;
// Indicates whether we were in the finished state during our
// most recent unthrottled sample (our last ComposeStyle call).

View File

@ -55,17 +55,12 @@ AnimationTimeline::GetCurrentTimeStamp() const
result = timing->GetNavigationStartTimeStamp();
}
nsIPresShell* presShell = mDocument->GetShell();
if (MOZ_UNLIKELY(!presShell)) {
nsRefreshDriver* refreshDriver = GetRefreshDriver();
if (!refreshDriver) {
return result;
}
nsPresContext* presContext = presShell->GetPresContext();
if (MOZ_UNLIKELY(!presContext)) {
return result;
}
result = presContext->RefreshDriver()->MostRecentRefresh();
result = refreshDriver->MostRecentRefresh();
// FIXME: We would like to assert that:
// mLastCurrentTime.IsNull() || result >= mLastCurrentTime
// but due to bug 1043078 this will not be the case when the refresh driver
@ -104,5 +99,21 @@ AnimationTimeline::ToTimeStamp(const TimeDuration& aTimeDuration) const
return result;
}
nsRefreshDriver*
AnimationTimeline::GetRefreshDriver() const
{
nsIPresShell* presShell = mDocument->GetShell();
if (MOZ_UNLIKELY(!presShell)) {
return nullptr;
}
nsPresContext* presContext = presShell->GetPresContext();
if (MOZ_UNLIKELY(!presContext)) {
return nullptr;
}
return presContext->RefreshDriver();
}
} // namespace dom
} // namespace mozilla

View File

@ -14,6 +14,7 @@
#include "nsIDocument.h"
struct JSContext;
class nsRefreshDriver;
namespace mozilla {
namespace dom {
@ -26,10 +27,17 @@ public:
{
}
protected:
virtual ~AnimationTimeline() { }
public:
NS_INLINE_DECL_CYCLE_COLLECTING_NATIVE_REFCOUNTING(AnimationTimeline)
NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_NATIVE_CLASS(AnimationTimeline)
nsISupports* GetParentObject() const { return mDocument; }
nsIGlobalObject* GetParentObject() const
{
return mDocument->GetParentObject();
}
virtual JSObject* WrapObject(JSContext* aCx) MOZ_OVERRIDE;
// AnimationTimeline methods
@ -44,8 +52,7 @@ public:
protected:
TimeStamp GetCurrentTimeStamp() const;
virtual ~AnimationTimeline() { }
nsRefreshDriver* GetRefreshDriver() const;
nsCOMPtr<nsIDocument> mDocument;

View File

@ -0,0 +1,35 @@
/* vim: set shiftwidth=2 tabstop=8 autoindent cindent expandtab: */
/* 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 "PendingPlayerTracker.h"
using namespace mozilla;
namespace mozilla {
NS_IMPL_CYCLE_COLLECTION(PendingPlayerTracker, mPlayPendingSet)
NS_IMPL_CYCLE_COLLECTION_ROOT_NATIVE(PendingPlayerTracker, AddRef)
NS_IMPL_CYCLE_COLLECTION_UNROOT_NATIVE(PendingPlayerTracker, Release)
void
PendingPlayerTracker::AddPlayPending(dom::AnimationPlayer& aPlayer)
{
mPlayPendingSet.PutEntry(&aPlayer);
}
void
PendingPlayerTracker::RemovePlayPending(dom::AnimationPlayer& aPlayer)
{
mPlayPendingSet.RemoveEntry(&aPlayer);
}
bool
PendingPlayerTracker::IsWaitingToPlay(dom::AnimationPlayer const& aPlayer) const
{
return mPlayPendingSet.Contains(const_cast<dom::AnimationPlayer*>(&aPlayer));
}
} // namespace mozilla

View File

@ -0,0 +1,36 @@
/* vim: set shiftwidth=2 tabstop=8 autoindent cindent expandtab: */
/* 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 mozilla_dom_PendingPlayerTracker_h
#define mozilla_dom_PendingPlayerTracker_h
#include "mozilla/dom/AnimationPlayer.h"
#include "nsCycleCollectionParticipant.h"
#include "nsTHashtable.h"
namespace mozilla {
class PendingPlayerTracker MOZ_FINAL
{
public:
NS_INLINE_DECL_CYCLE_COLLECTING_NATIVE_REFCOUNTING(PendingPlayerTracker)
NS_DECL_CYCLE_COLLECTION_NATIVE_CLASS(PendingPlayerTracker)
void AddPlayPending(dom::AnimationPlayer& aPlayer);
void RemovePlayPending(dom::AnimationPlayer& aPlayer);
bool IsWaitingToPlay(dom::AnimationPlayer const& aPlayer) const;
private:
~PendingPlayerTracker() { }
typedef nsTHashtable<nsRefPtrHashKey<dom::AnimationPlayer>>
AnimationPlayerSet;
AnimationPlayerSet mPlayPendingSet;
};
} // namespace mozilla
#endif // mozilla_dom_PendingPlayerTracker_h

View File

@ -14,11 +14,16 @@ EXPORTS.mozilla.dom += [
'AnimationTimeline.h',
]
EXPORTS.mozilla += [
'PendingPlayerTracker.h',
]
UNIFIED_SOURCES += [
'Animation.cpp',
'AnimationEffect.cpp',
'AnimationPlayer.cpp',
'AnimationTimeline.cpp',
'PendingPlayerTracker.cpp',
]
FAIL_ON_WARNINGS = True

View File

@ -2,6 +2,7 @@
<meta charset=utf-8>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="../testcommon.js"></script>
<div id="log"></div>
<style>
@keyframes xyz {
@ -11,35 +12,26 @@
<script>
'use strict';
function addDiv() {
var div = document.createElement('div');
document.body.appendChild(div);
return div;
}
test(function() {
var div = addDiv();
test(function(t) {
var div = addDiv(t);
div.style.animation = 'xyz 100s';
assert_equals(div.getAnimationPlayers()[0].source.effect.name, 'xyz',
'Animation effect name matches keyframes rule name');
div.remove();
}, 'Effect name makes keyframe rule');
test(function() {
var div = addDiv();
test(function(t) {
var div = addDiv(t);
div.style.animation = 'x\\yz 100s';
assert_equals(div.getAnimationPlayers()[0].source.effect.name, 'xyz',
'Escaped animation effect name matches keyframes rule name');
div.remove();
}, 'Escaped animation name');
test(function() {
var div = addDiv();
test(function(t) {
var div = addDiv(t);
div.style.animation = 'x\\79 z 100s';
assert_equals(div.getAnimationPlayers()[0].source.effect.name, 'xyz',
'Hex-escaped animation effect name matches keyframes rule'
+ ' name');
div.remove();
}, 'Animation name with hex-escape');
</script>

View File

@ -2,6 +2,7 @@
<meta charset=utf-8>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="../testcommon.js"></script>
<div id="log"></div>
<style>
@keyframes anim {
@ -12,24 +13,12 @@
<script>
'use strict';
function addDiv() {
var div = document.createElement('div');
document.body.appendChild(div);
return div;
}
function waitForFrame() {
return new Promise(function(resolve, reject) {
window.requestAnimationFrame(resolve);
});
}
function getMarginLeft(cs) {
return parseFloat(cs.marginLeft);
}
async_test(function(t) {
var div = addDiv();
var div = addDiv(t);
var cs = window.getComputedStyle(div);
div.style.animation = 'anim 1000s';
@ -39,27 +28,22 @@ async_test(function(t) {
'Initial value of margin-left is zero');
var previousAnimVal = getMarginLeft(cs);
waitForFrame().then(function() {
t.step(function() {
assert_true(getMarginLeft(cs) > previousAnimVal,
'margin-left is initially increasing');
previousAnimVal = getMarginLeft(cs);
player.ready.then(waitForFrame).then(t.step_func(function() {
assert_true(getMarginLeft(cs) > previousAnimVal,
'margin-left is initially increasing');
previousAnimVal = getMarginLeft(cs);
player.pause();
});
return waitForFrame();
}).then(function() {
t.step(function() {
assert_equals(getMarginLeft(cs), previousAnimVal,
'margin-left does not increase after calling pause()');
});
div.remove();
player.pause();
return player.ready.then(waitForFrame);
})).then(t.step_func(function() {
assert_equals(getMarginLeft(cs), previousAnimVal,
'margin-left does not increase after calling pause()');
t.done();
});
}));
}, 'pause() a running animation');
async_test(function(t) {
var div = addDiv();
var div = addDiv(t);
var cs = window.getComputedStyle(div);
div.style.animation = 'anim 1000s paused';
@ -69,22 +53,18 @@ async_test(function(t) {
player.pause();
div.style.animationPlayState = 'running';
cs.animationPlayState; // Trigger style resolution
waitForFrame().then(function() {
t.step(function() {
assert_equals(cs.animationPlayState, 'running',
'animation-play-state is running');
assert_equals(getMarginLeft(cs), 0,
'Paused value of margin-left is zero');
});
div.remove();
player.ready.then(waitForFrame).then(t.step_func(function() {
assert_equals(cs.animationPlayState, 'running',
'animation-play-state is running');
assert_equals(getMarginLeft(cs), 0,
'Paused value of margin-left is zero');
t.done();
});
}));
}, 'pause() overrides animation-play-state');
async_test(function(t) {
var div = addDiv();
var div = addDiv(t);
var cs = window.getComputedStyle(div);
div.style.animation = 'anim 1000s paused';
@ -95,18 +75,15 @@ async_test(function(t) {
player.play();
waitForFrame().then(function() {
t.step(function() {
assert_true(getMarginLeft(cs) > 0,
'Playing value of margin-left is greater than zero');
});
div.remove();
player.ready.then(waitForFrame).then(t.step_func(function() {
assert_true(getMarginLeft(cs) > 0,
'Playing value of margin-left is greater than zero');
t.done();
});
}));
}, 'play() overrides animation-play-state');
async_test(function(t) {
var div = addDiv();
var div = addDiv(t);
var cs = window.getComputedStyle(div);
div.style.animation = 'anim 1000s paused';
@ -115,35 +92,32 @@ async_test(function(t) {
'Initial value of margin-left is zero');
player.play();
div.style.animationPlayState = 'running';
cs.animationPlayState; // Trigger style resolution
var previousAnimVal;
waitForFrame().then(function() {
t.step(function() {
assert_equals(cs.animationPlayState, 'running',
'animation-play-state is running');
previousAnimVal = getMarginLeft(cs);
div.style.animationPlayState = 'paused';
cs.animationPlayState; // Trigger style resolution
});
player.ready.then(function() {
div.style.animationPlayState = 'running';
cs.animationPlayState; // Trigger style resolution
return waitForFrame();
}).then(function() {
t.step(function() {
assert_equals(cs.animationPlayState, 'paused',
'animation-play-state is paused');
assert_equals(getMarginLeft(cs), previousAnimVal,
'Animated value of margin-left does not change when'
+ ' paused by style');
});
div.remove();
}).then(t.step_func(function() {
assert_equals(cs.animationPlayState, 'running',
'animation-play-state is running');
previousAnimVal = getMarginLeft(cs);
div.style.animationPlayState = 'paused';
cs.animationPlayState; // Trigger style resolution
return waitForFrame();
})).then(t.step_func(function() {
assert_equals(cs.animationPlayState, 'paused',
'animation-play-state is paused');
assert_equals(getMarginLeft(cs), previousAnimVal,
'Animated value of margin-left does not change when'
+ ' paused by style');
t.done();
});
}));
}, 'play() is overridden by later setting "animation-play-state: paused"');
async_test(function(t) {
var div = addDiv();
var div = addDiv(t);
var cs = window.getComputedStyle(div);
div.style.animation = 'anim 1000s';
@ -158,20 +132,17 @@ async_test(function(t) {
player.play();
var previousAnimVal = getMarginLeft(cs);
waitForFrame().then(function() {
t.step(function() {
assert_equals(cs.animationPlayState, 'paused',
'animation-play-state is paused');
assert_true(getMarginLeft(cs) > previousAnimVal,
'Playing value of margin-left is increasing');
});
div.remove();
player.ready.then(waitForFrame).then(t.step_func(function() {
assert_equals(cs.animationPlayState, 'paused',
'animation-play-state is paused');
assert_true(getMarginLeft(cs) > previousAnimVal,
'Playing value of margin-left is increasing');
t.done();
});
}));
}, 'play() flushes pending changes to animation-play-state first');
async_test(function(t) {
var div = addDiv();
var div = addDiv(t);
var cs = window.getComputedStyle(div);
div.style.animation = 'anim 1000s paused';
@ -193,16 +164,13 @@ async_test(function(t) {
player.pause();
var previousAnimVal = getMarginLeft(cs);
waitForFrame().then(function() {
t.step(function() {
assert_equals(cs.animationPlayState, 'running',
'animation-play-state is paused');
assert_equals(getMarginLeft(cs), previousAnimVal,
'Paused value of margin-left does not change');
});
div.remove();
waitForFrame().then(t.step_func(function() {
assert_equals(cs.animationPlayState, 'running',
'animation-play-state is paused');
assert_equals(getMarginLeft(cs), previousAnimVal,
'Paused value of margin-left does not change');
t.done();
});
}));
}, 'pause() applies pending changes to animation-play-state first');
// (Note that we can't actually test for this; see comment above, in test-body.)

View File

@ -2,6 +2,7 @@
<meta charset=utf-8>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="../testcommon.js"></script>
<div id="log"></div>
<style>
@keyframes anim { }
@ -9,23 +10,23 @@
<script>
'use strict';
function addDiv() {
var div = document.createElement('div');
document.body.appendChild(div);
return div;
}
test(function() {
var div = addDiv();
async_test(function(t) {
var div = addDiv(t);
var cs = window.getComputedStyle(div);
div.style.animation = 'anim 1000s';
var player = div.getAnimationPlayers()[0];
assert_equals(player.playState, 'running');
// Bug 927349: Check for pending state here
player.ready.then(t.step_func(function() {
assert_equals(player.playState, 'running');
t.done();
}));
}, 'Player returns correct playState when running');
test(function() {
var div = addDiv();
test(function(t) {
var div = addDiv(t);
var cs = window.getComputedStyle(div);
div.style.animation = 'anim 1000s paused';
@ -33,8 +34,8 @@ test(function() {
assert_equals(player.playState, 'paused');
}, 'Player returns correct playState when paused');
test(function() {
var div = addDiv();
test(function(t) {
var div = addDiv(t);
var cs = window.getComputedStyle(div);
div.style.animation = 'anim 1000s';
@ -43,14 +44,15 @@ test(function() {
assert_equals(player.playState, 'paused');
}, 'Player.playState updates when paused by script');
test(function() {
var div = addDiv();
test(function(t) {
var div = addDiv(t);
var cs = window.getComputedStyle(div);
div.style.animation = 'anim 1000s paused';
var player = div.getAnimationPlayers()[0];
div.style.animationPlayState = 'running';
// This test also checks that calling playState flushes style
// Bug 927349: Make this check for 'pending'
assert_equals(player.playState, 'running');
}, 'Player.playState updates when resumed by setting style');

View File

@ -0,0 +1,78 @@
<!doctype html>
<meta charset=utf-8>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="../testcommon.js"></script>
<div id="log"></div>
<style>
@keyframes abc {
to { transform: translate(10px) }
}
</style>
<script>
'use strict';
async_test(function(t) {
var div = addDiv(t);
div.style.animation = 'abc 100s';
var player = div.getAnimationPlayers()[0];
var originalReadyPromise = player.ready;
player.ready.then(function() {
assert_equals(player.ready, originalReadyPromise,
'Ready promise is the same object when playing completes');
player.pause();
// TODO: When we implement deferred pausing (bug 1109390), change this to
// assert_not_equals and wait on the new promise before continuing.
assert_equals(player.ready, originalReadyPromise,
'Ready promise does not change when pausing (for now)');
player.play();
assert_not_equals(player.ready, originalReadyPromise,
'Ready promise object identity differs after calling'
+ ' play()');
t.done();
});
}, 'A new ready promise is created each time play() is called'
+ ' the animation property');
test(function(t) {
var div = addDiv(t);
div.style.animation = 'abc 100s paused';
var player = div.getAnimationPlayers()[0];
var originalReadyPromise = player.ready;
div.style.animationPlayState = 'running';
assert_not_equals(player.ready, originalReadyPromise,
'After updating animation-play-state a new ready promise'
+ ' object is created');
}, 'A new ready promise is created when setting animation-play-state: running');
async_test(function(t) {
var div = addDiv(t);
div.style.animation = 'abc 100s';
var player = div.getAnimationPlayers()[0];
player.ready.then(function() {
var promiseBeforeCallingPlay = player.ready;
player.play();
assert_equals(player.ready, promiseBeforeCallingPlay,
'Ready promise has same object identity after redundant call'
+ ' to play()');
t.done();
});
}, 'Redundant calls to play() do not generate new ready promise objects');
async_test(function(t) {
var div = addDiv(t);
div.style.animation = 'abc 100s';
var player = div.getAnimationPlayers()[0];
player.ready.then(function(resolvedPlayer) {
assert_equals(resolvedPlayer, player,
'Object identity of player passed to Promise callback'
+ ' matches the player object owning the Promise');
t.done();
});
}, 'The ready promise is fulfilled with its AnimationPlayer');
</script>

View File

@ -2,6 +2,7 @@
<meta charset=utf-8>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="../testcommon.js"></script>
<div id="log"></div>
<style>
@keyframes anim { }
@ -9,19 +10,12 @@
<script>
'use strict';
function addDiv() {
var div = document.createElement('div');
document.body.appendChild(div);
return div;
}
test(function() {
var div = addDiv();
test(function(t) {
var div = addDiv(t);
div.style.animation = 'anim 100s';
var players = div.getAnimationPlayers();
assert_equals(players[0].source.target, div,
'Animation.target is the animatable div');
div.remove();
}, 'Returned CSS animations have the correct Animation.target');
</script>

View File

@ -2,6 +2,7 @@
<meta charset=utf-8>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="../testcommon.js"></script>
<div id="log"></div>
<style>
@keyframes anim1 {
@ -12,23 +13,24 @@
<script>
'use strict';
function addDiv() {
var div = document.createElement('div');
document.body.appendChild(div);
return div;
}
async_test(function(t) {
var div = addDiv();
var div = addDiv(t);
div.style.animation = 'anim1 100s';
var originalPlayer = div.getAnimationPlayers()[0];
var originalStartTime = originalPlayer.startTime;
var originalCurrentTime = originalPlayer.currentTime;
var originalStartTime;
var originalCurrentTime;
// Wait a moment so we can confirm the startTime doesn't change (and doesn't
// simply reflect the current time).
window.requestAnimationFrame(t.step_func(function() {
originalPlayer.ready.then(function() {
originalStartTime = originalPlayer.startTime;
originalCurrentTime = originalPlayer.currentTime;
// Wait a moment so we can confirm the startTime doesn't change (and
// doesn't simply reflect the current time).
return waitForFrame();
}).then(t.step_func(function() {
div.style.animationDuration = '200s';
var player = div.getAnimationPlayers()[0];
assert_equals(player, originalPlayer,
@ -41,13 +43,12 @@ async_test(function(t) {
assert_not_equals(player.currentTime, originalCurrentTime,
'AnimationPlayer.currentTime has updated in next'
+ ' requestAnimationFrame callback');
div.remove();
t.done();
}));
}, 'AnimationPlayers preserve their startTime when changed');
test(function() {
var div = addDiv();
test(function(t) {
var div = addDiv(t);
div.style.animation = 'anim1 100s, anim1 100s';
// Store original state
@ -65,7 +66,7 @@ test(function() {
}, 'Updated AnimationPlayers maintain their order in the list');
async_test(function(t) {
var div = addDiv();
var div = addDiv(t);
div.style.animation = 'anim1 200s, anim1 100s';
// Store original state
@ -73,8 +74,10 @@ async_test(function(t) {
var player1 = players[0];
var player2 = players[1];
// Wait before continuing so we can compare start times
window.requestAnimationFrame(t.step_func(function() {
// Wait before continuing so we can compare start times (otherwise the
// new player objects and existing player objects will all have the same
// start time).
waitForAllPlayers(players).then(waitForFrame).then(t.step_func(function() {
// Swap duration of first and second in list and prepend animation at the
// same time
div.style.animation = 'anim1 100s, anim1 100s, anim1 200s';
@ -87,63 +90,69 @@ async_test(function(t) {
'Second player is in third position after update');
assert_equals(players[1].startTime, players[2].startTime,
'Old players have the same start time');
// TODO: Check that players[0].startTime === null
return players[0].ready;
})).then(t.step_func(function() {
assert_true(players[0].startTime > players[1].startTime,
'New player has later start time');
div.remove();
t.done();
}));
}, 'Only the startTimes of existing animations are preserved');
async_test(function(t) {
var div = addDiv();
var div = addDiv(t);
div.style.animation = 'anim1 100s, anim1 100s';
var secondPlayer = div.getAnimationPlayers()[1];
// Wait before continuing so we can compare start times
window.requestAnimationFrame(t.step_func(function() {
secondPlayer.ready.then(waitForFrame).then(t.step_func(function() {
// Trim list of animations
div.style.animationName = 'anim1';
var players = div.getAnimationPlayers();
assert_equals(players.length, 1, 'List of players was trimmed');
assert_equals(players[0], secondPlayer,
'Remaining player is the second one in the list');
assert_equals(typeof(players[0].startTime), 'number',
'Remaining player has resolved startTime');
assert_true(players[0].startTime < players[0].timeline.currentTime,
'Remaining player preserves startTime');
div.remove();
t.done();
}));
}, 'Animations are removed from the start of the list while preserving'
+ ' the state of existing players');
async_test(function(t) {
var div = addDiv();
var div = addDiv(t);
div.style.animation = 'anim1 100s';
var firstAddedPlayer = div.getAnimationPlayers()[0];
var firstAddedPlayer = div.getAnimationPlayers()[0],
secondAddedPlayer,
players;
// Wait and add second player
window.requestAnimationFrame(t.step_func(function() {
firstAddedPlayer.ready.then(waitForFrame).then(t.step_func(function() {
div.style.animation = 'anim1 100s, anim1 100s';
var secondAddedPlayer = div.getAnimationPlayers()[0];
secondAddedPlayer = div.getAnimationPlayers()[0];
// Wait again and add another player
window.requestAnimationFrame(t.step_func(function() {
div.style.animation = 'anim1 100s, anim2 100s, anim1 100s';
var players = div.getAnimationPlayers();
assert_not_equals(firstAddedPlayer, secondAddedPlayer,
'New players are added to start of the list');
assert_equals(players[0], secondAddedPlayer,
'Second player remains in same position after'
+ ' interleaving');
assert_equals(players[2], firstAddedPlayer,
'First player remains in same position after'
+ ' interleaving');
assert_true(players[1].startTime > players[0].startTime,
'Interleaved player starts later than existing players');
assert_true(players[0].startTime > players[2].startTime,
'Original players retain their start time');
div.remove();
t.done();
}));
return secondAddedPlayer.ready.then(waitForFrame);
})).then(t.step_func(function() {
div.style.animation = 'anim1 100s, anim2 100s, anim1 100s';
players = div.getAnimationPlayers();
assert_not_equals(firstAddedPlayer, secondAddedPlayer,
'New players are added to start of the list');
assert_equals(players[0], secondAddedPlayer,
'Second player remains in same position after'
+ ' interleaving');
assert_equals(players[2], firstAddedPlayer,
'First player remains in same position after'
+ ' interleaving');
return players[1].ready;
})).then(t.step_func(function() {
assert_true(players[1].startTime > players[0].startTime,
'Interleaved player starts later than existing players');
assert_true(players[0].startTime > players[2].startTime,
'Original players retain their start time');
t.done();
}));
}, 'Player state is preserved when interleaving animations in list');

View File

@ -2,6 +2,7 @@
<meta charset=utf-8>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="../testcommon.js"></script>
<div id="log"></div>
<style>
@keyframes anim1 {
@ -18,95 +19,94 @@
<script>
'use strict';
function addDiv() {
var div = document.createElement('div');
document.body.appendChild(div);
return div;
}
test(function() {
var div = addDiv();
test(function(t) {
var div = addDiv(t);
assert_equals(div.getAnimationPlayers().length, 0,
'getAnimationPlayers returns an empty sequence for an element'
+ ' with no animations');
div.remove();
}, 'getAnimationPlayers for non-animated content');
async_test(function(t) {
var div = addDiv();
var div = addDiv(t);
// Add an animation
div.style.animation = 'anim1 100s';
var players = div.getAnimationPlayers();
assert_equals(players.length, 1,
'getAnimationPlayers returns a player running CSS Animations');
var startTime = players[0].startTime;
assert_true(startTime > 0 && startTime <= document.timeline.currentTime,
'CSS animation has a sensible start time');
players[0].ready.then(t.step_func(function() {
var startTime = players[0].startTime;
assert_true(startTime > 0 && startTime <= document.timeline.currentTime,
'CSS animation has a sensible start time');
// Wait a moment then add a second animation.
//
// We wait for the next frame so that we can test that the start times of
// the animations differ.
window.requestAnimationFrame(t.step_func(function() {
// Wait a moment then add a second animation.
//
// We wait for the next frame so that we can test that the start times of
// the animations differ.
return waitForFrame();
})).then(t.step_func(function() {
div.style.animation = 'anim1 100s, anim2 100s';
players = div.getAnimationPlayers();
assert_equals(players.length, 2,
'getAnimationPlayers returns one player for each value of'
+ ' animation-name');
// Wait until both players are ready
// (We don't make any assumptions about the order of the players since
// that is the purpose of the following test.)
return waitForAllPlayers(players);
})).then(t.step_func(function() {
assert_true(players[0].startTime < players[1].startTime,
'Additional players for CSS animations start after the original'
+ ' animation and appear later in the list');
div.remove();
t.done();
}));
}, 'getAnimationPlayers for CSS Animations');
test(function() {
var div = addDiv();
test(function(t) {
var div = addDiv(t);
// Add an animation that targets multiple properties
div.style.animation = 'multiPropAnim 100s';
assert_equals(div.getAnimationPlayers().length, 1,
'getAnimationPlayers returns only one player for a CSS Animation'
+ ' that targets multiple properties');
div.remove();
}, 'getAnimationPlayers for multi-property animations');
async_test(function(t) {
var div = addDiv();
var div = addDiv(t);
// Add an animation
div.style.backgroundColor = 'red';
div.style.animation = 'anim1 100s';
window.getComputedStyle(div).backgroundColor;
// Wait a moment then add a transition
window.requestAnimationFrame(t.step_func(function() {
// Wait until a frame after the animation starts, then add a transition
var players = div.getAnimationPlayers();
players[0].ready.then(waitForFrame).then(t.step_func(function() {
div.style.transition = 'all 100s';
div.style.backgroundColor = 'green';
var players = div.getAnimationPlayers();
players = div.getAnimationPlayers();
assert_equals(players.length, 2,
'getAnimationPlayers returns players for both animations and '
'getAnimationPlayers returns players for both animations and'
+ ' transitions that run simultaneously');
return waitForAllPlayers(players);
})).then(t.step_func(function() {
assert_true(players[0].startTime > players[1].startTime,
'players for transitions appear before animations even if they '
'players for transitions appear before animations even if they'
+ ' start later');
div.remove();
t.done();
}));
}, 'getAnimationPlayers for both CSS Animations and Transitions at once');
async_test(function(t) {
var div = addDiv();
var div = addDiv(t);
// Set up event listener
div.addEventListener('animationend', t.step_func(function() {
assert_equals(div.getAnimationPlayers().length, 0,
'getAnimationPlayers does not return players for finished '
+ ' (and non-forwards-filling) CSS Animations');
div.remove();
t.done();
}));
@ -115,14 +115,13 @@ async_test(function(t) {
}, 'getAnimationPlayers for CSS Animations that have finished');
async_test(function(t) {
var div = addDiv();
var div = addDiv(t);
// Set up event listener
div.addEventListener('animationend', t.step_func(function() {
assert_equals(div.getAnimationPlayers().length, 1,
'getAnimationPlayers returns players for CSS Animations that have'
+ ' finished but are filling forwards');
div.remove();
t.done();
}));
@ -131,8 +130,8 @@ async_test(function(t) {
}, 'getAnimationPlayers for CSS Animations that have finished but are'
+ ' forwards filling');
test(function() {
var div = addDiv();
test(function(t) {
var div = addDiv(t);
div.style.animation = 'none 100s';
var players = div.getAnimationPlayers();
@ -145,12 +144,10 @@ test(function() {
assert_equals(players.length, 1,
'getAnimationPlayers returns players only for those CSS Animations whose'
+ ' animation-name is not none');
div.remove();
}, 'getAnimationPlayers for CSS Animations with animation-name: none');
test(function() {
var div = addDiv();
test(function(t) {
var div = addDiv(t);
div.style.animation = 'missing 100s';
var players = div.getAnimationPlayers();
assert_equals(players.length, 0,
@ -162,67 +159,66 @@ test(function() {
assert_equals(players.length, 1,
'getAnimationPlayers returns players only for those CSS Animations whose'
+ ' animation-name is found');
div.remove();
}, 'getAnimationPlayers for CSS Animations with animation-name: missing');
async_test(function(t) {
var div = addDiv();
var div = addDiv(t);
div.style.animation = 'anim1 100s, notyet 100s';
var players = div.getAnimationPlayers();
assert_equals(players.length, 1,
'getAnimationPlayers initally only returns players for CSS Animations whose'
+ ' animation-name is found');
window.requestAnimationFrame(t.step_func(function() {
players[0].ready.then(waitForFrame).then(t.step_func(function() {
var keyframes = '@keyframes notyet { to { left: 100px; } }';
document.styleSheets[0].insertRule(keyframes, 0);
players = div.getAnimationPlayers();
assert_equals(players.length, 2,
'getAnimationPlayers includes player when @keyframes rule is added'
+ ' later');
return waitForAllPlayers(players);
})).then(t.step_func(function() {
assert_true(players[0].startTime < players[1].startTime,
'Newly added player has a later start time');
document.styleSheets[0].deleteRule(0);
div.remove();
t.done();
}));
}, 'getAnimationPlayers for CSS Animations where the @keyframes rule is added'
+ ' later');
test(function() {
var div = addDiv();
test(function(t) {
var div = addDiv(t);
div.style.animation = 'anim1 100s, anim1 100s';
assert_equals(div.getAnimationPlayers().length, 2,
'getAnimationPlayers returns one player for each CSS animation-name'
+ ' even if the names are duplicated');
div.remove();
}, 'getAnimationPlayers for CSS Animations with duplicated animation-name');
test(function() {
var div = addDiv();
test(function(t) {
var div = addDiv(t);
div.style.animation = 'empty 100s';
assert_equals(div.getAnimationPlayers().length, 1,
'getAnimationPlayers returns players for CSS animations with an'
+ ' empty keyframes rule');
div.remove();
}, 'getAnimationPlayers for CSS Animations with empty keyframes rule');
test(function() {
var div = addDiv();
async_test(function(t) {
var div = addDiv(t);
div.style.animation = 'anim1 100s 100s';
var players = div.getAnimationPlayers();
assert_equals(players.length, 1,
'getAnimationPlayers returns animations for CSS animations whose'
+ ' delay makes them start later');
assert_true(players[0].startTime <= document.timeline.currentTime,
'For CSS Animations in delay phase, the start time of the player is'
+ ' not in the future');
div.remove();
players[0].ready.then(waitForFrame).then(t.step_func(function() {
assert_true(players[0].startTime <= document.timeline.currentTime,
'For CSS Animations in delay phase, the start time of the player is'
+ ' not in the future');
t.done();
}));
}, 'getAnimationPlayers for CSS animations in delay phase');
test(function() {
var div = addDiv();
test(function(t) {
var div = addDiv(t);
div.style.animation = 'anim1 0s 100s';
assert_equals(div.getAnimationPlayers().length, 1,
'getAnimationPlayers returns animations for CSS animations whose'
@ -230,8 +226,8 @@ test(function() {
div.remove();
}, 'getAnimationPlayers for zero-duration CSS Animations');
test(function() {
var div = addDiv();
test(function(t) {
var div = addDiv(t);
div.style.animation = 'anim1 100s';
var originalPlayer = div.getAnimationPlayers()[0];
@ -252,8 +248,6 @@ test(function() {
assert_equals(originalPlayer, extendedPlayer,
'getAnimationPlayers returns the same objects even when their'
+ ' duration changes');
div.remove();
}, 'getAnimationPlayers returns objects with the same identity');
</script>

View File

@ -2,13 +2,13 @@
<meta charset=utf-8>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="../testcommon.js"></script>
<div id="log"></div>
<script>
'use strict';
test(function() {
var div = document.createElement('div');
document.body.appendChild(div);
test(function(t) {
var div = addDiv(t);
// Add a transition
div.style.left = '0px';
@ -18,7 +18,6 @@ test(function() {
assert_equals(div.getAnimationPlayers()[0].source.effect.name, '',
'Animation effects for transitions have an empty name');
div.remove();
}, 'Effect name for transitions');
</script>

View File

@ -2,28 +2,17 @@
<meta charset=utf-8>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="../testcommon.js"></script>
<div id="log"></div>
<script>
'use strict';
function addDiv() {
var div = document.createElement('div');
document.body.appendChild(div);
return div;
}
function waitForFrame() {
return new Promise(function(resolve, reject) {
window.requestAnimationFrame(resolve);
});
}
function getMarginLeft(cs) {
return parseFloat(cs.marginLeft);
}
async_test(function(t) {
var div = addDiv();
var div = addDiv(t);
var cs = window.getComputedStyle(div);
div.style.marginLeft = '0px';
@ -37,30 +26,23 @@ async_test(function(t) {
'Initial value of margin-left is zero');
var previousAnimVal = getMarginLeft(cs);
waitForFrame().then(function() {
t.step(function() {
assert_true(getMarginLeft(cs) > previousAnimVal,
'margin-left is initially increasing');
previousAnimVal = getMarginLeft(cs);
player.pause();
});
player.ready.then(waitForFrame).then(t.step_func(function() {
assert_true(getMarginLeft(cs) > previousAnimVal,
'margin-left is initially increasing');
previousAnimVal = getMarginLeft(cs);
player.pause();
return waitForFrame();
}).then(function() {
t.step(function() {
assert_equals(getMarginLeft(cs), previousAnimVal,
'margin-left does not increase after calling pause()');
previousAnimVal = getMarginLeft(cs);
player.play();
});
return waitForFrame();
}).then(function() {
t.step(function() {
assert_true(getMarginLeft(cs) > previousAnimVal,
'margin-left increases after calling play()');
});
div.remove();
})).then(t.step_func(function() {
assert_equals(getMarginLeft(cs), previousAnimVal,
'margin-left does not increase after calling pause()');
previousAnimVal = getMarginLeft(cs);
player.play();
return player.ready.then(waitForFrame);
})).then(t.step_func(function() {
assert_true(getMarginLeft(cs) > previousAnimVal,
'margin-left increases after calling play()');
t.done();
});
}));
}, 'pause() and play() a transition');
</script>

View File

@ -0,0 +1,38 @@
<!doctype html>
<meta charset=utf-8>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="../testcommon.js"></script>
<div id="log"></div>
<script>
'use strict';
async_test(function(t) {
var div = addDiv(t);
div.style.transform = 'translate(0px)';
window.getComputedStyle(div).transform;
div.style.transition = 'transform 100s';
div.style.transform = 'translate(10px)';
window.getComputedStyle(div).transform;
var player = div.getAnimationPlayers()[0];
var originalReadyPromise = player.ready;
player.ready.then(t.step_func(function() {
assert_equals(player.ready, originalReadyPromise,
'Ready promise is the same object when playing completes');
player.pause();
// TODO: When we implement deferred pausing, change this to
// assert_not_equals and wait on the new promise before continuing.
assert_equals(player.ready, originalReadyPromise,
'Ready promise does not change when pausing (for now)');
player.play();
assert_not_equals(player.ready, originalReadyPromise,
'Ready promise object identity differs after calling'
+ ' play()');
t.done();
}));
}, 'A new ready promise is created each time play() is called'
+ ' the animation property');
</script>

View File

@ -2,13 +2,13 @@
<meta charset=utf-8>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="../testcommon.js"></script>
<div id="log"></div>
<script>
'use strict';
test(function() {
var div = document.createElement('div');
document.body.appendChild(div);
test(function(t) {
var div = addDiv(t);
div.style.left = '0px';
window.getComputedStyle(div).transitionProperty;
@ -18,7 +18,6 @@ test(function() {
var players = div.getAnimationPlayers();
assert_equals(players[0].source.target, div,
'Animation.target is the animatable div');
div.remove();
}, 'Returned CSS transitions have the correct Animation.target');
</script>

View File

@ -2,18 +2,13 @@
<meta charset=utf-8>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="../testcommon.js"></script>
<div id="log"></div>
<script>
'use strict';
function addDiv() {
var div = document.createElement('div');
document.body.appendChild(div);
return div;
}
async_test(function(t) {
var div = addDiv();
var div = addDiv(t);
// Add a couple of transitions
div.style.left = '0px';
@ -27,34 +22,35 @@ async_test(function(t) {
var players = div.getAnimationPlayers();
assert_equals(players.length, 2,
'getAnimationPlayers() returns one player per transitioning property');
var startTime = players[0].startTime;
assert_true(startTime > 0 && startTime <= document.timeline.currentTime,
'CSS transitions have sensible start times');
assert_equals(players[0].startTime, players[1].startTime,
'CSS transitions started together have the same start time');
// Wait a moment then add a third transition
window.requestAnimationFrame(t.step_func(function() {
waitForAllPlayers(players).then(t.step_func(function() {
var startTime = players[0].startTime;
assert_true(startTime > 0 && startTime <= document.timeline.currentTime,
'CSS transitions have sensible start times');
assert_equals(players[0].startTime, players[1].startTime,
'CSS transitions started together have the same start time');
// Wait a moment then add a third transition
return waitForFrame();
})).then(t.step_func(function() {
div.style.backgroundColor = 'green';
players = div.getAnimationPlayers();
assert_equals(players.length, 3,
'getAnimationPlayers returns players for all running CSS Transitions');
return waitForAllPlayers(players);
})).then(t.step_func(function() {
assert_true(players[1].startTime < players[2].startTime,
'Player for additional CSS transition starts after the original'
+ ' transitions and appears later in the list');
div.remove();
t.done();
}));
}, 'getAnimationPlayers for CSS Transitions');
async_test(function(t) {
var div = addDiv();
var div = addDiv(t);
// Set up event listener
div.addEventListener('transitionend', t.step_func(function() {
assert_equals(div.getAnimationPlayers().length, 0,
'getAnimationPlayers does not return finished CSS Transitions');
div.remove();
t.done();
}));
@ -67,8 +63,8 @@ async_test(function(t) {
window.getComputedStyle(div).left;
}, 'getAnimationPlayers for CSS Transitions that have finished');
test(function() {
var div = addDiv();
test(function(t) {
var div = addDiv(t);
// Try to transition non-animatable property animation-duration
div.style.animationDuration = '10s';
@ -80,11 +76,10 @@ test(function() {
assert_equals(div.getAnimationPlayers().length, 0,
'getAnimationPlayers returns an empty sequence for a transition'
+ ' of a non-animatable property');
div.remove();
}, 'getAnimationPlayers for transition on non-animatable property');
test(function() {
var div = addDiv();
test(function(t) {
var div = addDiv(t);
div.style.setProperty('-vendor-unsupported', '0px', '');
window.getComputedStyle(div).transitionProperty;
@ -94,7 +89,6 @@ test(function() {
assert_equals(div.getAnimationPlayers().length, 0,
'getAnimationPlayers returns an empty sequence for a transition'
+ ' of an unsupported property');
div.remove();
}, 'getAnimationPlayers for transition on unsupported property');
</script>

View File

@ -1,14 +1,20 @@
[DEFAULT]
support-files =
testcommon.js
[animation-timeline/test_animation-timeline.html]
skip-if = buildapp == 'mulet'
[css-animations/test_animations-dynamic-changes.html]
[css-animations/test_animation-effect-name.html]
[css-animations/test_animation-pausing.html]
[css-animations/test_animation-player-playstate.html]
[css-animations/test_animation-player-ready.html]
[css-animations/test_animation-target.html]
[css-animations/test_element-get-animation-players.html]
skip-if = buildapp == 'mulet'
[css-transitions/test_animation-effect-name.html]
[css-transitions/test_animation-pausing.html]
[css-transitions/test_animation-player-ready.html]
[css-transitions/test_animation-target.html]
[css-transitions/test_element-get-animation-players.html]
skip-if = buildapp == 'mulet'

View File

@ -0,0 +1,36 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/ */
/**
* Appends a div to the document body.
*
* @param t The testharness.js Test object. If provided, this will be used
* to register a cleanup callback to remove the div when the test
* finishes.
*/
function addDiv(t) {
var div = document.createElement('div');
document.body.appendChild(div);
if (t && typeof t.add_cleanup === 'function') {
t.add_cleanup(function() { div.remove(); });
}
return div;
}
/**
* Promise wrapper for requestAnimationFrame.
*/
function waitForFrame() {
return new Promise(function(resolve, reject) {
window.requestAnimationFrame(resolve);
});
}
/**
* Wrapper that takes a sequence of N players and returns:
*
* Promise.all([players[0].ready, players[1].ready, ... players[N-1].ready]);
*/
function waitForAllPlayers(players) {
return Promise.all(players.map(function(player) { return player.ready; }));
}

View File

@ -505,7 +505,16 @@ this.DOMApplicationRegistry = {
// Installs a 3rd party app.
installPreinstalledApp: function installPreinstalledApp(aId) {
#ifdef MOZ_WIDGET_GONK
let app = this.webapps[aId];
// In some cases, the app might be already installed under a different ID but
// with the same manifestURL. In that case, the only content of the webapp will
// be the id of the old version, which is the one we'll keep.
let destId = this.webapps[aId].oldId || aId;
// We don't need the oldId anymore
if (destId !== aId) {
delete this.webapps[aId];
}
let app = this.webapps[destId];
let baseDir, isPreinstalled = false;
try {
baseDir = FileUtils.getDir("coreAppsDir", ["webapps", aId], false);
@ -545,10 +554,10 @@ this.DOMApplicationRegistry = {
}
debug("Installing 3rd party app : " + aId +
" from " + baseDir.path);
" from " + baseDir.path + " to " + destId);
// We copy this app to DIRECTORY_NAME/$aId, and set the base path as needed.
let destDir = FileUtils.getDir(DIRECTORY_NAME, ["webapps", aId], true, true);
// We copy this app to DIRECTORY_NAME/$destId, and set the base path as needed.
let destDir = FileUtils.getDir(DIRECTORY_NAME, ["webapps", destId], true, true);
filesToMove.forEach(function(aFile) {
let file = baseDir.clone();
@ -568,7 +577,7 @@ this.DOMApplicationRegistry = {
return isPreinstalled;
}
app.origin = "app://" + aId;
app.origin = "app://" + destId;
// Do this for all preinstalled apps... we can't know at this
// point if the updates will be signed or not and it doesn't
@ -593,7 +602,7 @@ this.DOMApplicationRegistry = {
// If we are unable to extract the manifest, cleanup and remove this app.
debug("Cleaning up: " + e);
destDir.remove(true);
delete this.webapps[aId];
delete this.webapps[destId];
} finally {
zipReader.close();
}
@ -667,7 +676,13 @@ this.DOMApplicationRegistry = {
for (let id in data) {
// Core apps have ids matching their domain name (eg: dialer.gaiamobile.org)
// Use that property to check if they are new or not.
if (!(id in this.webapps)) {
// Note that in some cases, the id might change, but the
// manifest URL wont. So consider that the app is old if
// the id does not exist already and if there's no other id
// for the manifestURL.
var oldId = (id in this.webapps) ? id :
this._appIdForManifestURL(data[id].manifestURL);
if (!oldId) {
this.webapps[id] = data[id];
this.webapps[id].basePath = appDir.path;
@ -688,11 +703,17 @@ this.DOMApplicationRegistry = {
// we fall into this case if the app is present in /system/b2g/webapps/webapps.json
// and in /data/local/webapps/webapps.json: this happens when updating gaia apps
// Confere bug 989876
// We also should fall in this case when the app is a preinstalled third party app.
for (let field in data[id]) {
if (fieldsBlacklist.indexOf(field) === -1) {
this.webapps[id][field] = data[id][field];
this.webapps[oldId][field] = data[id][field];
}
}
// If the id for the app has changed on the update, keep a pointer to the old one
// since we'll need this to update the app files.
if (id !== oldId) {
this.webapps[id] = {oldId: oldId};
}
}
}
}.bind(this)).then(null, Cu.reportError);

View File

@ -569,8 +569,6 @@ AutoIncumbentScript::AutoIncumbentScript(nsIGlobalObject* aGlobalObject)
AutoNoJSAPI::AutoNoJSAPI(bool aIsMainThread)
: ScriptSettingsStackEntry()
{
MOZ_ASSERT_IF(nsContentUtils::GetCurrentJSContextForThread(),
!JS_IsExceptionPending(nsContentUtils::GetCurrentJSContextForThread()));
if (aIsMainThread) {
mCxPusher.emplace(static_cast<JSContext*>(nullptr),
/* aAllowNull = */ true);

View File

@ -375,7 +375,7 @@ nsDOMWindowUtils::SetDisplayPortForElement(float aXPx, float aYPx,
new DisplayPortPropertyData(displayport, aPriority),
nsINode::DeleteProperty<DisplayPortPropertyData>);
if (nsLayoutUtils::UsesAsyncScrolling()) {
if (nsLayoutUtils::UsesAsyncScrolling() && gfxPrefs::LayoutUseContainersForRootFrames()) {
nsIFrame* rootScrollFrame = presShell->GetRootScrollFrame();
if (rootScrollFrame && content == rootScrollFrame->GetContent()) {
// We are setting a root displayport for a document.

View File

@ -1995,6 +1995,7 @@ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INTERNAL(nsDocument)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mStateObjectCached)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mUndoManager)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mAnimationTimeline)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPendingPlayerTracker)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mTemplateContentsOwner)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mChildrenCollection)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mRegistry)
@ -2078,6 +2079,7 @@ NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(nsDocument)
NS_IMPL_CYCLE_COLLECTION_UNLINK(mCachedEncoder)
NS_IMPL_CYCLE_COLLECTION_UNLINK(mUndoManager)
NS_IMPL_CYCLE_COLLECTION_UNLINK(mAnimationTimeline)
NS_IMPL_CYCLE_COLLECTION_UNLINK(mPendingPlayerTracker)
NS_IMPL_CYCLE_COLLECTION_UNLINK(mTemplateContentsOwner)
NS_IMPL_CYCLE_COLLECTION_UNLINK(mChildrenCollection)
NS_IMPL_CYCLE_COLLECTION_UNLINK(mRegistry)
@ -5266,7 +5268,7 @@ nsIDocument::RemoveAnonymousContent(AnonymousContent& aContent,
ErrorResult& aRv)
{
nsIPresShell* shell = GetShell();
if (!shell) {
if (!shell || !shell->GetCanvasFrame()) {
aRv.Throw(NS_ERROR_UNEXPECTED);
return;
}
@ -7391,6 +7393,16 @@ nsDocument::GetAnimationController()
return mAnimationController;
}
PendingPlayerTracker*
nsDocument::GetOrCreatePendingPlayerTracker()
{
if (!mPendingPlayerTracker) {
mPendingPlayerTracker = new PendingPlayerTracker();
}
return mPendingPlayerTracker;
}
/**
* Retrieve the "direction" property of the document.
*

View File

@ -59,6 +59,7 @@
#include "mozilla/EventListenerManager.h"
#include "mozilla/EventStates.h"
#include "mozilla/MemoryReporting.h"
#include "mozilla/PendingPlayerTracker.h"
#include "mozilla/dom/DOMImplementation.h"
#include "mozilla/dom/StyleSheetList.h"
#include "nsDataHashtable.h"
@ -1047,6 +1048,15 @@ public:
// If HasAnimationController is true, this is guaranteed to return non-null.
nsSMILAnimationController* GetAnimationController() MOZ_OVERRIDE;
virtual mozilla::PendingPlayerTracker*
GetPendingPlayerTracker() MOZ_FINAL
{
return mPendingPlayerTracker;
}
virtual mozilla::PendingPlayerTracker*
GetOrCreatePendingPlayerTracker() MOZ_OVERRIDE;
void SetImagesNeedAnimating(bool aAnimating) MOZ_OVERRIDE;
virtual void SuppressEventHandling(SuppressionType aWhat,
@ -1511,6 +1521,10 @@ protected:
// Array of observers
nsTObserverArray<nsIDocumentObserver*> mObservers;
// Tracker for animation players that are waiting to start.
// nullptr until GetOrCreatePendingPlayerTracker is called.
nsRefPtr<mozilla::PendingPlayerTracker> mPendingPlayerTracker;
// Weak reference to the scope object (aka the script global object)
// that, unlike mScriptGlobalObject, is never unset once set. This
// is a weak reference to avoid leaks due to circular references.

View File

@ -86,6 +86,7 @@ namespace mozilla {
class CSSStyleSheet;
class ErrorResult;
class EventStates;
class PendingPlayerTracker;
class SVGAttrAnimationRuleProcessor;
namespace css {
@ -1822,6 +1823,17 @@ public:
// mAnimationController isn't yet initialized.
virtual nsSMILAnimationController* GetAnimationController() = 0;
// Gets the tracker for animation players that are waiting to start.
// Returns nullptr if there is no pending player tracker for this document
// which will be the case if there have never been any CSS animations or
// transitions on elements in the document.
virtual mozilla::PendingPlayerTracker* GetPendingPlayerTracker() = 0;
// Gets the tracker for animation players that are waiting to start and
// creates it if it doesn't already exist. As a result, the return value
// will never be nullptr.
virtual mozilla::PendingPlayerTracker* GetOrCreatePendingPlayerTracker() = 0;
// Makes the images on this document capable of having their animation
// active or suspended. An Image will animate as long as at least one of its
// owning Documents needs it to animate; otherwise it can suspend.

View File

@ -20,10 +20,11 @@ using namespace mozilla;
CameraControlImpl::CameraControlImpl()
: mListenerLock(PR_NewRWLock(PR_RWLOCK_RANK_NONE, "CameraControlImpl.Listeners.Lock"))
, mPreviewState(CameraControlListener::kPreviewStopped)
, mHardwareState(CameraControlListener::kHardwareClosed)
, mHardwareState(CameraControlListener::kHardwareUninitialized)
, mHardwareStateChangeReason(NS_OK)
{
DOM_CAMERA_LOGT("%s:%d : this=%p\n", __func__, __LINE__, this);
mCurrentConfiguration.mMode = ICameraControl::kUnspecifiedMode;
// reuse the same camera thread to conserve resources
nsCOMPtr<nsIThread> ct = do_QueryInterface(sCameraThread);
@ -79,7 +80,7 @@ CameraControlImpl::OnHardwareStateChange(CameraControlListener::HardwareState aN
}
#ifdef PR_LOGGING
const char* state[] = { "closed", "open", "failed" };
const char* state[] = { "uninitialized", "closed", "open", "failed" };
MOZ_ASSERT(aNewState >= 0);
if (static_cast<unsigned int>(aNewState) < sizeof(state) / sizeof(state[0])) {
DOM_CAMERA_LOGI("New hardware state is '%s' (reason=0x%x)\n",

View File

@ -34,6 +34,7 @@ public:
enum HardwareState
{
kHardwareUninitialized,
kHardwareClosed,
kHardwareOpen,
kHardwareOpenFailed

View File

@ -202,6 +202,7 @@ nsDOMCameraControl::nsDOMCameraControl(uint32_t aCameraId,
, mGetCameraPromise(aPromise)
, mWindow(aWindow)
, mPreviewState(CameraControlListener::kPreviewStopped)
, mSetInitialConfig(false)
{
DOM_CAMERA_LOGT("%s:%d : this=%p\n", __func__, __LINE__, this);
mInput = new CameraPreviewMediaStream(this);
@ -236,8 +237,14 @@ nsDOMCameraControl::nsDOMCameraControl(uint32_t aCameraId,
}
if (haveInitialConfig) {
config.mPreviewSize.width = aInitialConfig.mPreviewSize.mWidth;
config.mPreviewSize.height = aInitialConfig.mPreviewSize.mHeight;
rv = SelectPreviewSize(aInitialConfig.mPreviewSize, config.mPreviewSize);
if (NS_FAILED(rv)) {
mListener->OnUserError(DOMCameraControlListener::kInStartCamera, rv);
return;
}
config.mPictureSize.width = aInitialConfig.mPictureSize.mWidth;
config.mPictureSize.height = aInitialConfig.mPictureSize.mHeight;
config.mRecorderProfile = aInitialConfig.mRecorderProfile;
}
@ -274,6 +281,9 @@ nsDOMCameraControl::nsDOMCameraControl(uint32_t aCameraId,
// Start the camera...
if (haveInitialConfig) {
rv = mCameraControl->Start(&config);
if (NS_SUCCEEDED(rv)) {
mSetInitialConfig = true;
}
} else {
rv = mCameraControl->Start();
}
@ -281,6 +291,9 @@ nsDOMCameraControl::nsDOMCameraControl(uint32_t aCameraId,
} else {
if (haveInitialConfig) {
rv = mCameraControl->SetConfiguration(config);
if (NS_SUCCEEDED(rv)) {
mSetInitialConfig = true;
}
} else {
rv = NS_OK;
}
@ -308,6 +321,46 @@ nsDOMCameraControl::IsWindowStillActive()
return nsDOMCameraManager::IsWindowStillActive(mWindow->WindowID());
}
nsresult
nsDOMCameraControl::SelectPreviewSize(const CameraSize& aRequestedPreviewSize, ICameraControl::Size& aSelectedPreviewSize)
{
if (aRequestedPreviewSize.mWidth && aRequestedPreviewSize.mHeight) {
aSelectedPreviewSize.width = aRequestedPreviewSize.mWidth;
aSelectedPreviewSize.height = aRequestedPreviewSize.mHeight;
} else {
/* Use the window width and height if no preview size is provided.
Note that the width and height are actually reversed from the
camera perspective. */
int32_t width = 0;
int32_t height = 0;
float ratio = 0.0;
nsresult rv;
rv = mWindow->GetDevicePixelRatio(&ratio);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
rv = mWindow->GetInnerWidth(&height);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
rv = mWindow->GetInnerHeight(&width);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
MOZ_ASSERT(width > 0);
MOZ_ASSERT(height > 0);
MOZ_ASSERT(ratio > 0.0);
aSelectedPreviewSize.width = std::ceil(width * ratio);
aSelectedPreviewSize.height = std::ceil(height * ratio);
}
return NS_OK;
}
// Setter for weighted regions: { top, bottom, left, right, weight }
nsresult
nsDOMCameraControl::Set(uint32_t aKey, const Optional<Sequence<CameraRegion> >& aValue, uint32_t aLimit)
@ -801,9 +854,14 @@ nsDOMCameraControl::SetConfiguration(const CameraConfiguration& aConfiguration,
}
ICameraControl::Configuration config;
aRv = SelectPreviewSize(aConfiguration.mPreviewSize, config.mPreviewSize);
if (aRv.Failed()) {
return nullptr;
}
config.mRecorderProfile = aConfiguration.mRecorderProfile;
config.mPreviewSize.width = aConfiguration.mPreviewSize.mWidth;
config.mPreviewSize.height = aConfiguration.mPreviewSize.mHeight;
config.mPictureSize.width = aConfiguration.mPictureSize.mWidth;
config.mPictureSize.height = aConfiguration.mPictureSize.mHeight;
config.mMode = ICameraControl::kPictureMode;
if (aConfiguration.mMode == CameraMode::Video) {
config.mMode = ICameraControl::kVideoMode;
@ -1041,6 +1099,20 @@ nsDOMCameraControl::DispatchStateEvent(const nsString& aType, const nsString& aS
DispatchTrustedEvent(event);
}
void
nsDOMCameraControl::OnGetCameraComplete()
{
// The hardware is open, so we can return a camera to JS, even if
// the preview hasn't started yet.
nsRefPtr<Promise> promise = mGetCameraPromise.forget();
if (promise) {
CameraGetPromiseData data;
data.mCamera = this;
data.mConfiguration = *mCurrentConfiguration;
promise->MaybeResolve(data);
}
}
// Camera Control event handlers--must only be called from the Main Thread!
void
nsDOMCameraControl::OnHardwareStateChange(CameraControlListener::HardwareState aState,
@ -1055,22 +1127,16 @@ nsDOMCameraControl::OnHardwareStateChange(CameraControlListener::HardwareState a
case CameraControlListener::kHardwareOpen:
DOM_CAMERA_LOGI("DOM OnHardwareStateChange: open\n");
MOZ_ASSERT(aReason == NS_OK);
{
if (!mSetInitialConfig) {
// The hardware is open, so we can return a camera to JS, even if
// the preview hasn't started yet.
nsRefPtr<Promise> promise = mGetCameraPromise.forget();
if (promise) {
CameraGetPromiseData data;
data.mCamera = this;
data.mConfiguration = *mCurrentConfiguration;
promise->MaybeResolve(data);
}
OnGetCameraComplete();
}
break;
case CameraControlListener::kHardwareClosed:
DOM_CAMERA_LOGI("DOM OnHardwareStateChange: closed\n");
{
if (!mSetInitialConfig) {
nsRefPtr<Promise> promise = mReleasePromise.forget();
if (promise) {
promise->MaybeResolve(JS::UndefinedHandleValue);
@ -1102,6 +1168,9 @@ nsDOMCameraControl::OnHardwareStateChange(CameraControlListener::HardwareState a
NS_LITERAL_STRING("close"),
eventInit);
DispatchTrustedEvent(event);
} else {
// The configuration failed and we forced the camera to shutdown.
OnUserError(DOMCameraControlListener::kInStartCamera, NS_ERROR_NOT_AVAILABLE);
}
break;
@ -1111,6 +1180,9 @@ nsDOMCameraControl::OnHardwareStateChange(CameraControlListener::HardwareState a
OnUserError(DOMCameraControlListener::kInStartCamera, NS_ERROR_NOT_AVAILABLE);
break;
case CameraControlListener::kHardwareUninitialized:
break;
default:
DOM_CAMERA_LOGE("DOM OnHardwareStateChange: UNKNOWN=%d\n", aState);
MOZ_ASSERT_UNREACHABLE("Unanticipated camera hardware state");
@ -1227,9 +1299,17 @@ nsDOMCameraControl::OnConfigurationChange(DOMCameraConfiguration* aConfiguration
mCurrentConfiguration->mMaxMeteringAreas);
DOM_CAMERA_LOGI(" preview size (w x h) : %d x %d\n",
mCurrentConfiguration->mPreviewSize.mWidth, mCurrentConfiguration->mPreviewSize.mHeight);
DOM_CAMERA_LOGI(" picture size (w x h) : %d x %d\n",
mCurrentConfiguration->mPictureSize.mWidth, mCurrentConfiguration->mPictureSize.mHeight);
DOM_CAMERA_LOGI(" recorder profile : %s\n",
NS_ConvertUTF16toUTF8(mCurrentConfiguration->mRecorderProfile).get());
if (mSetInitialConfig) {
OnGetCameraComplete();
mSetInitialConfig = false;
return;
}
nsRefPtr<Promise> promise = mSetConfigurationPromise.forget();
if (promise) {
promise->MaybeResolve(*aConfiguration);
@ -1241,6 +1321,9 @@ nsDOMCameraControl::OnConfigurationChange(DOMCameraConfiguration* aConfiguration
eventInit.mPreviewSize = new DOMRect(static_cast<DOMMediaStream*>(this), 0, 0,
mCurrentConfiguration->mPreviewSize.mWidth,
mCurrentConfiguration->mPreviewSize.mHeight);
eventInit.mPictureSize = new DOMRect(static_cast<DOMMediaStream*>(this), 0, 0,
mCurrentConfiguration->mPictureSize.mWidth,
mCurrentConfiguration->mPictureSize.mHeight);
nsRefPtr<CameraConfigurationEvent> event =
CameraConfigurationEvent::Constructor(this,
@ -1359,6 +1442,16 @@ nsDOMCameraControl::OnUserError(CameraControlListener::UserContext aContext, nsr
break;
case CameraControlListener::kInSetConfiguration:
if (mSetInitialConfig) {
// If the SetConfiguration() call in the constructor fails, there
// is nothing we can do except release the camera hardware. This
// will trigger a hardware state change, and when the flag that
// got us here is set in that handler, we replace the normal reason
// code with one that indicates the hardware isn't available.
DOM_CAMERA_LOGI("Failed to configure cached camera, stopping\n");
mCameraControl->Stop();
return;
}
promise = mSetConfigurationPromise.forget();
break;

View File

@ -171,6 +171,7 @@ protected:
void OnTakePictureComplete(nsIDOMBlob* aPicture);
void OnFacesDetected(const nsTArray<ICameraControl::Face>& aFaces);
void OnGetCameraComplete();
void OnHardwareStateChange(DOMCameraControlListener::HardwareState aState, nsresult aReason);
void OnPreviewStateChange(DOMCameraControlListener::PreviewState aState);
void OnRecorderStateChange(CameraControlListener::RecorderState aState, int32_t aStatus, int32_t aTrackNum);
@ -179,6 +180,7 @@ protected:
void OnUserError(CameraControlListener::UserContext aContext, nsresult aError);
bool IsWindowStillActive();
nsresult SelectPreviewSize(const dom::CameraSize& aRequestedPreviewSize, ICameraControl::Size& aSelectedPreviewSize);
nsresult NotifyRecordingStatusChange(const nsString& aMsg);
@ -222,6 +224,8 @@ protected:
nsRefPtr<DeviceStorageFileDescriptor> mDSFileDescriptor;
DOMCameraControlListener::PreviewState mPreviewState;
bool mSetInitialConfig;
#ifdef MOZ_WIDGET_GONK
// cached camera control, to improve start-up time
static StaticRefPtr<ICameraControl> sCachedCameraControl;

View File

@ -215,6 +215,8 @@ DOMCameraControlListener::OnConfigurationChange(const CameraListenerConfiguratio
config->mRecorderProfile = mConfiguration.mRecorderProfile;
config->mPreviewSize.mWidth = mConfiguration.mPreviewSize.width;
config->mPreviewSize.mHeight = mConfiguration.mPreviewSize.height;
config->mPictureSize.mWidth = mConfiguration.mPictureSize.width;
config->mPictureSize.mHeight = mConfiguration.mPictureSize.height;
config->mMaxMeteringAreas = mConfiguration.mMaxMeteringAreas;
config->mMaxFocusAreas = mConfiguration.mMaxFocusAreas;

View File

@ -62,7 +62,6 @@ using namespace android;
// Construct nsGonkCameraControl on the main thread.
nsGonkCameraControl::nsGonkCameraControl(uint32_t aCameraId)
: mCameraId(aCameraId)
, mLastPictureSize({0, 0})
, mLastThumbnailSize({0, 0})
, mPreviewFps(30)
, mResumePreviewAfterTakingPicture(false) // XXXmikeh - see bug 950102
@ -136,10 +135,16 @@ nsGonkCameraControl::StartInternal(const Configuration* aInitialConfig)
}
OnHardwareStateChange(CameraControlListener::kHardwareOpen, NS_OK);
if (aInitialConfig) {
return StartPreviewImpl();
}
if (aInitialConfig) {
rv = StartPreviewInternal();
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
OnConfigurationChange();
OnPreviewStateChange(CameraControlListener::kPreviewStarted);
}
return NS_OK;
}
@ -180,7 +185,7 @@ nsGonkCameraControl::Initialize()
mParams.Get(CAMERA_PARAM_SUPPORTED_MAXFOCUSAREAS, areas);
mCurrentConfiguration.mMaxFocusAreas = areas != -1 ? areas : 0;
mParams.Get(CAMERA_PARAM_PICTURE_SIZE, mLastPictureSize);
mParams.Get(CAMERA_PARAM_PICTURE_SIZE, mCurrentConfiguration.mPictureSize);
mParams.Get(CAMERA_PARAM_PREVIEWSIZE, mCurrentConfiguration.mPreviewSize);
nsString luminance; // check for support
@ -197,7 +202,7 @@ nsGonkCameraControl::Initialize()
DOM_CAMERA_LOGI(" - maximum metering areas: %u\n", mCurrentConfiguration.mMaxMeteringAreas);
DOM_CAMERA_LOGI(" - maximum focus areas: %u\n", mCurrentConfiguration.mMaxFocusAreas);
DOM_CAMERA_LOGI(" - default picture size: %u x %u\n",
mLastPictureSize.width, mLastPictureSize.height);
mCurrentConfiguration.mPictureSize.width, mCurrentConfiguration.mPictureSize.height);
DOM_CAMERA_LOGI(" - default picture file format: %s\n",
NS_ConvertUTF16toUTF8(mFileFormat).get());
DOM_CAMERA_LOGI(" - default picture quality: %f\n", quality);
@ -222,6 +227,11 @@ nsGonkCameraControl::Initialize()
mParams.Get(CAMERA_PARAM_VIDEOSIZE, mLastRecorderSize);
DOM_CAMERA_LOGI(" - default video recorder size: %u x %u\n",
mLastRecorderSize.width, mLastRecorderSize.height);
Size preferred;
mParams.Get(CAMERA_PARAM_PREFERRED_PREVIEWSIZE_FOR_VIDEO, preferred);
DOM_CAMERA_LOGI(" - preferred video preview size: %u x %u\n",
preferred.width, preferred.height);
} else {
mLastRecorderSize = mCurrentConfiguration.mPreviewSize;
}
@ -261,23 +271,66 @@ nsGonkCameraControl::~nsGonkCameraControl()
DOM_CAMERA_LOGT("%s:%d\n", __func__, __LINE__);
}
nsresult
nsGonkCameraControl::ValidateConfiguration(const Configuration& aConfig, Configuration& aValidatedConfig)
{
nsAutoTArray<Size, 16> supportedSizes;
Get(CAMERA_PARAM_SUPPORTED_PICTURESIZES, supportedSizes);
nsresult rv = GetSupportedSize(aConfig.mPictureSize, supportedSizes,
aValidatedConfig.mPictureSize);
if (NS_FAILED(rv)) {
DOM_CAMERA_LOGW("Unable to find a picture size close to %ux%u\n",
aConfig.mPictureSize.width, aConfig.mPictureSize.height);
return NS_ERROR_INVALID_ARG;
}
rv = LoadRecorderProfiles();
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
nsString profileName = aConfig.mRecorderProfile;
if (profileName.IsEmpty()) {
profileName.AssignASCII("default");
}
RecorderProfile* profile;
if (!mRecorderProfiles.Get(profileName, &profile)) {
DOM_CAMERA_LOGE("Recorder profile '%s' is not supported\n",
NS_ConvertUTF16toUTF8(aConfig.mRecorderProfile).get());
return NS_ERROR_INVALID_ARG;
}
aValidatedConfig.mMode = aConfig.mMode;
aValidatedConfig.mPreviewSize = aConfig.mPreviewSize;
aValidatedConfig.mRecorderProfile = profile->GetName();
return NS_OK;
}
nsresult
nsGonkCameraControl::SetConfigurationInternal(const Configuration& aConfig)
{
DOM_CAMERA_LOGT("%s:%d\n", __func__, __LINE__);
nsresult rv;
// Ensure sanity of all provided parameters and determine defaults if
// none are provided when given a new configuration
Configuration config;
nsresult rv = ValidateConfiguration(aConfig, config);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
{
ICameraControlParameterSetAutoEnter set(this);
switch (aConfig.mMode) {
switch (config.mMode) {
case kPictureMode:
rv = SetPictureConfiguration(aConfig);
rv = SetPictureConfiguration(config);
break;
case kVideoMode:
rv = SetVideoConfiguration(aConfig);
rv = SetVideoConfiguration(config);
break;
default:
@ -291,16 +344,15 @@ nsGonkCameraControl::SetConfigurationInternal(const Configuration& aConfig)
return rv;
}
rv = Set(CAMERA_PARAM_RECORDINGHINT, aConfig.mMode == kVideoMode);
rv = Set(CAMERA_PARAM_RECORDINGHINT, config.mMode == kVideoMode);
if (NS_FAILED(rv)) {
DOM_CAMERA_LOGE("Failed to set recording hint (0x%x)\n", rv);
}
}
mCurrentConfiguration.mMode = aConfig.mMode;
mCurrentConfiguration.mRecorderProfile = aConfig.mRecorderProfile;
OnConfigurationChange();
mCurrentConfiguration.mMode = config.mMode;
mCurrentConfiguration.mRecorderProfile = config.mRecorderProfile;
mCurrentConfiguration.mPictureSize = config.mPictureSize;
return NS_OK;
}
@ -335,7 +387,18 @@ nsGonkCameraControl::SetConfigurationImpl(const Configuration& aConfig)
// Restart the preview
DOM_CAMERA_LOGT("%s:%d\n", __func__, __LINE__);
return StartPreviewImpl();
rv = StartPreviewInternal();
if (NS_WARN_IF(NS_FAILED(rv))) {
StopPreviewImpl();
return rv;
}
// OnConfigurationChange() indicates the success case of this operation.
// It must not be fired until all intermediate steps, including starting
// the preview, have completed successfully.
OnConfigurationChange();
OnPreviewStateChange(CameraControlListener::kPreviewStarted);
return NS_OK;
}
nsresult
@ -412,39 +475,29 @@ nsGonkCameraControl::SetPictureConfiguration(const Configuration& aConfig)
DOM_CAMERA_LOGT("%s:%d\n", __func__, __LINE__);
MOZ_ASSERT(NS_GetCurrentThread() == mCameraThread);
nsTArray<Size> sizes;
nsresult rv = Get(CAMERA_PARAM_SUPPORTED_PREVIEWSIZES, sizes);
Size max({0, 0});
nsresult rv = SelectCaptureAndPreviewSize(aConfig.mPreviewSize,
aConfig.mPictureSize, max,
CAMERA_PARAM_PICTURE_SIZE);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
Size preview;
rv = GetSupportedSize(aConfig.mPreviewSize, sizes, preview);
if (NS_FAILED(rv)) {
DOM_CAMERA_LOGE(
"Failed to find a supported preview size, requested size %ux%u (0x%x)",
aConfig.mPreviewSize.width, aConfig.mPreviewSize.height, rv);
return rv;
}
rv = Set(CAMERA_PARAM_PREVIEWSIZE, preview);
if (NS_FAILED(rv)) {
DOM_CAMERA_LOGE("Failed to set supported preview size %ux%u (0x%x)",
preview.width, preview.height, rv);
return rv;
}
mCurrentConfiguration.mPreviewSize = preview;
if (mSeparateVideoAndPreviewSizesSupported) {
MaybeAdjustVideoSize();
}
rv = UpdateThumbnailSize();
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
mParams.Get(CAMERA_PARAM_PREVIEWFRAMERATE, mPreviewFps);
DOM_CAMERA_LOGI("picture mode preview: wanted %ux%u, got %ux%u (%u fps)\n",
aConfig.mPreviewSize.width, aConfig.mPreviewSize.height,
preview.width, preview.height,
mCurrentConfiguration.mPreviewSize.width,
mCurrentConfiguration.mPreviewSize.height,
mPreviewFps);
return NS_OK;
@ -683,7 +736,7 @@ nsGonkCameraControl::SetLocation(const Position& aLocation)
}
nsresult
nsGonkCameraControl::StartPreviewImpl()
nsGonkCameraControl::StartPreviewInternal()
{
MOZ_ASSERT(NS_GetCurrentThread() == mCameraThread);
RETURN_IF_NO_CAMERA_HW();
@ -702,10 +755,19 @@ nsGonkCameraControl::StartPreviewImpl()
return NS_ERROR_FAILURE;
}
OnPreviewStateChange(CameraControlListener::kPreviewStarted);
return NS_OK;
}
nsresult
nsGonkCameraControl::StartPreviewImpl()
{
nsresult rv = StartPreviewInternal();
if (NS_SUCCEEDED(rv)) {
OnPreviewStateChange(CameraControlListener::kPreviewStarted);
}
return rv;
}
nsresult
nsGonkCameraControl::StopPreviewImpl()
{
@ -814,8 +876,8 @@ nsGonkCameraControl::SetThumbnailSizeImpl(const Size& aSize)
if (area != 0 &&
delta < smallestDelta &&
supportedSizes[i].width * mLastPictureSize.height ==
mLastPictureSize.width * supportedSizes[i].height) {
supportedSizes[i].width * mCurrentConfiguration.mPictureSize.height ==
mCurrentConfiguration.mPictureSize.width * supportedSizes[i].height) {
smallestDelta = delta;
smallestDeltaIndex = i;
}
@ -898,7 +960,8 @@ nsGonkCameraControl::SetPictureSizeImpl(const Size& aSize)
return NS_ERROR_INVALID_ARG;
}
if (aSize.width == mLastPictureSize.width && aSize.height == mLastPictureSize.height) {
if (aSize.width == mCurrentConfiguration.mPictureSize.width &&
aSize.height == mCurrentConfiguration.mPictureSize.height) {
DOM_CAMERA_LOGI("Requested picture size %ux%u unchanged\n", aSize.width, aSize.height);
return NS_OK;
}
@ -926,7 +989,7 @@ nsGonkCameraControl::SetPictureSizeImpl(const Size& aSize)
return rv;
}
mLastPictureSize = best;
mCurrentConfiguration.mPictureSize = best;
// Finally, update the thumbnail size in case the picture aspect ratio changed.
// Some drivers will fail to take a picture if the thumbnail size is not the
@ -1377,6 +1440,11 @@ nsGonkCameraControl::GetSupportedSize(const Size& aSize,
uint32_t minSizeDelta = UINT32_MAX;
uint32_t delta;
if (aSupportedSizes.IsEmpty()) {
// no valid sizes
return rv;
}
if (!aSize.width && !aSize.height) {
// no size specified, take the first supported size
best = aSupportedSizes[0];
@ -1432,91 +1500,41 @@ nsGonkCameraControl::GetSupportedSize(const Size& aSize,
}
nsresult
nsGonkCameraControl::SetVideoAndPreviewSize(const Size& aPreviewSize, const Size& aVideoSize)
nsGonkCameraControl::SelectCaptureAndPreviewSize(const Size& aPreviewSize,
const Size& aCaptureSize,
const Size& aMaxSize,
uint32_t aCaptureSizeKey)
{
MOZ_ASSERT(NS_GetCurrentThread() == mCameraThread);
MOZ_ASSERT(mSeparateVideoAndPreviewSizesSupported);
DOM_CAMERA_LOGI("Setting video size to %ux%u, preview size to %ux%u\n",
aVideoSize.width, aVideoSize.height,
aPreviewSize.width, aPreviewSize.height);
// At this point, we know the capture size has been validated and replaced
// if necessary with the best matching supported value.
DOM_CAMERA_LOGI("Select capture size %ux%u, preview size %ux%u, maximum size %ux%u\n",
aCaptureSize.width, aCaptureSize.height,
aPreviewSize.width, aPreviewSize.height,
aMaxSize.width, aMaxSize.height);
Size oldSize;
nsresult rv = Get(CAMERA_PARAM_PREVIEWSIZE, oldSize);
nsAutoTArray<Size, 16> sizes;
nsresult rv = Get(CAMERA_PARAM_SUPPORTED_PREVIEWSIZES, sizes);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
rv = Set(CAMERA_PARAM_PREVIEWSIZE, aPreviewSize);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
rv = Set(CAMERA_PARAM_VIDEOSIZE, aVideoSize);
if (NS_WARN_IF(NS_FAILED(rv))) {
Set(CAMERA_PARAM_VIDEOSIZE, oldSize); // error, try to restore the original preview size
return rv;
// May optionally apply a ceiling to the preview size. Any supported preview
// size with an area larger than the maximum will be ignored regardless of
// aspect ratio or delta to requested preview size.
uint32_t maxArea = aMaxSize.width * aMaxSize.height;
if (maxArea == 0) {
maxArea = UINT32_MAX;
}
mCurrentConfiguration.mPreviewSize = aPreviewSize;
mLastRecorderSize = aVideoSize;
const uint32_t previewArea = aPreviewSize.width * aPreviewSize.height;
return NS_OK;
}
nsresult
nsGonkCameraControl::SelectVideoAndPreviewSize(const Configuration& aConfig, const Size& aVideoSize)
{
MOZ_ASSERT(NS_GetCurrentThread() == mCameraThread);
MOZ_ASSERT(mSeparateVideoAndPreviewSizesSupported);
nsTArray<Size> sizes;
nsresult rv = Get(CAMERA_PARAM_SUPPORTED_VIDEOSIZES, sizes);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
Size video;
rv = GetSupportedSize(aVideoSize, sizes, video);
if (NS_FAILED(rv)) {
DOM_CAMERA_LOGE("Failed to find a supported video size, requested size %ux%u",
aVideoSize.width, aVideoSize.height);
return rv;
}
rv = Get(CAMERA_PARAM_SUPPORTED_PREVIEWSIZES, sizes);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
Size preview;
rv = GetSupportedSize(aConfig.mPreviewSize, sizes, preview);
if (NS_FAILED(rv)) {
DOM_CAMERA_LOGE("Failed to find a supported preview size, requested size %ux%u",
aConfig.mPreviewSize.width, aConfig.mPreviewSize.height);
return rv;
}
Size preferred;
rv = Get(CAMERA_PARAM_PREFERRED_PREVIEWSIZE_FOR_VIDEO, preferred);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
// If the requested preview size has the same aspect ratio as the
// requested video size, *and* is the same size or smaller than
// the preferred video size, then we're done.
const uint32_t preferredArea = preferred.width * preferred.height;
if (video.width * aConfig.mPreviewSize.height == aConfig.mPreviewSize.width * video.height &&
preview.width * preview.height <= preferredArea) {
// We're done: set the video and preview sizes and return...
return SetVideoAndPreviewSize(preview, video);
}
// Otherwise, if the requested preview size is larger than the preferred
// size, or there is an aspect ratio mismatch, then we need to set the
// preview size to the closest size smaller than the preferred size,
// preferably with the same aspect ratio as the requested video size.
// We should select a preview size with the same aspect ratio as the capture
// size and minimize the delta with the requested preview size. If we are
// unable to find any supported preview sizes which match the aspect ratio
// of the capture size, we fallback to only minimizing the delta with the
// requested preview size.
SizeIndex bestSizeMatch = 0; // initializers to keep warnings away
SizeIndex bestSizeMatchWithAspectRatio = 0;
@ -1528,13 +1546,19 @@ nsGonkCameraControl::SelectVideoAndPreviewSize(const Configuration& aConfig, con
for (SizeIndex i = 0; i < sizes.Length(); ++i) {
const Size& s = sizes[i];
const uint32_t area = s.width * s.height;
if (area > preferredArea) {
// preview size must be smaller or equal to the capture size
if (aCaptureSize.width < s.width || aCaptureSize.height < s.height) {
continue;
}
const uint32_t delta = preferredArea - area;
if (s.width * video.height == video.width * s.height) {
const uint32_t area = s.width * s.height;
if (area > maxArea) {
continue;
}
const uint32_t delta = abs(static_cast<long int>(previewArea - area));
if (s.width * aCaptureSize.height == aCaptureSize.width * s.height) {
if (delta == 0) {
// exact match, including aspect ratio--we can stop now
bestSizeMatchWithAspectRatio = i;
@ -1553,19 +1577,41 @@ nsGonkCameraControl::SelectVideoAndPreviewSize(const Configuration& aConfig, con
}
}
Size previewSize;
if (foundSizeMatchWithAspectRatio) {
preview = sizes[bestSizeMatchWithAspectRatio];
previewSize = sizes[bestSizeMatchWithAspectRatio];
} else if (foundSizeMatch) {
DOM_CAMERA_LOGW("Unable to match a preview size with aspect ratio of video size %ux%u\n",
video.width, video.height);
preview = sizes[bestSizeMatch];
DOM_CAMERA_LOGW("Unable to match a preview size with aspect ratio of capture size %ux%u\n",
aCaptureSize.width, aCaptureSize.height);
previewSize = sizes[bestSizeMatch];
} else {
DOM_CAMERA_LOGE("Unable to find a preview size for video size %ux%u\n",
video.width, video.height);
DOM_CAMERA_LOGE("Unable to find a preview size for capture size %ux%u\n",
aCaptureSize.width, aCaptureSize.height);
return NS_ERROR_INVALID_ARG;
}
return SetVideoAndPreviewSize(preview, video);
DOM_CAMERA_LOGI("Setting capture size to %ux%u, preview size to %ux%u\n",
aCaptureSize.width, aCaptureSize.height,
previewSize.width, previewSize.height);
Size oldSize;
rv = Get(CAMERA_PARAM_PREVIEWSIZE, oldSize);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
rv = Set(CAMERA_PARAM_PREVIEWSIZE, previewSize);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
rv = Set(aCaptureSizeKey, aCaptureSize);
if (NS_WARN_IF(NS_FAILED(rv))) {
Set(CAMERA_PARAM_PREVIEWSIZE, oldSize); // error, try to restore the original preview size
return rv;
}
mCurrentConfiguration.mPreviewSize = previewSize;
return NS_OK;
}
nsresult
@ -1573,13 +1619,6 @@ nsGonkCameraControl::SetVideoConfiguration(const Configuration& aConfig)
{
DOM_CAMERA_LOGT("%s:%d\n", __func__, __LINE__);
// The application may cache an old configuration and already have
// a desired recorder profile without checking the capabilities first
nsresult rv = LoadRecorderProfiles();
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
RecorderProfile* profile;
if (!mRecorderProfiles.Get(aConfig.mRecorderProfile, &profile)) {
DOM_CAMERA_LOGE("Recorder profile '%s' is not supported\n",
@ -1605,7 +1644,14 @@ nsGonkCameraControl::SetVideoConfiguration(const Configuration& aConfig)
if (mSeparateVideoAndPreviewSizesSupported) {
// The camera supports two video streams: a low(er) resolution preview
// stream and and a potentially high(er) resolution stream for encoding.
rv = SelectVideoAndPreviewSize(aConfig, size);
Size preferred;
rv = Get(CAMERA_PARAM_PREFERRED_PREVIEWSIZE_FOR_VIDEO, preferred);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
rv = SelectCaptureAndPreviewSize(aConfig.mPreviewSize, size, preferred,
CAMERA_PARAM_VIDEOSIZE);
if (NS_FAILED(rv)) {
DOM_CAMERA_LOGE("Failed to set video and preview sizes (0x%x)\n", rv);
return rv;
@ -1623,6 +1669,8 @@ nsGonkCameraControl::SetVideoConfiguration(const Configuration& aConfig)
mCurrentConfiguration.mPreviewSize = size;
}
mLastRecorderSize = size;
rv = Set(CAMERA_PARAM_PREVIEWFRAMERATE, static_cast<int>(fps));
if (NS_FAILED(rv)) {
DOM_CAMERA_LOGE("Failed to set video mode frame rate (0x%x)\n", rv);
@ -1903,9 +1951,12 @@ nsGonkCameraControl::LoadRecorderProfiles()
return NS_ERROR_NOT_AVAILABLE;
}
nsTArray<RecorderProfile>::size_type bestIndexMatch = 0;
int bestAreaMatch = 0;
// Limit profiles to those video sizes supported by the camera hardware...
for (nsTArray<RecorderProfile>::size_type i = 0; i < profiles.Length(); ++i) {
int width = profiles[i]->GetVideo().GetSize().width;
int width = profiles[i]->GetVideo().GetSize().width;
int height = profiles[i]->GetVideo().GetSize().height;
if (width < 0 || height < 0) {
DOM_CAMERA_LOGW("Ignoring weird profile '%s' with width and/or height < 0\n",
@ -1916,10 +1967,22 @@ nsGonkCameraControl::LoadRecorderProfiles()
if (static_cast<uint32_t>(width) == sizes[n].width &&
static_cast<uint32_t>(height) == sizes[n].height) {
mRecorderProfiles.Put(profiles[i]->GetName(), profiles[i]);
int area = width * height;
if (area > bestAreaMatch) {
bestIndexMatch = i;
bestAreaMatch = area;
}
break;
}
}
}
// Default profile is the one with the largest area.
if (bestAreaMatch > 0) {
nsAutoString name;
name.AssignASCII("default");
mRecorderProfiles.Put(name, profiles[bestIndexMatch]);
}
}
return NS_OK;

View File

@ -105,10 +105,12 @@ protected:
nsresult Initialize();
nsresult ValidateConfiguration(const Configuration& aConfig, Configuration& aValidatedConfig);
nsresult SetConfigurationInternal(const Configuration& aConfig);
nsresult SetPictureConfiguration(const Configuration& aConfig);
nsresult SetVideoConfiguration(const Configuration& aConfig);
nsresult StartInternal(const Configuration* aInitialConfig);
nsresult StartPreviewInternal();
nsresult StopInternal();
template<class T> nsresult SetAndPush(uint32_t aKey, const T& aValue);
@ -133,8 +135,8 @@ protected:
nsresult SetupRecording(int aFd, int aRotation, uint64_t aMaxFileSizeBytes,
uint64_t aMaxVideoLengthMs);
nsresult SetupRecordingFlash(bool aAutoEnableLowLightTorch);
nsresult SelectVideoAndPreviewSize(const Configuration& aConfig, const Size& aVideoSize);
nsresult SetVideoAndPreviewSize(const Size& aPreviewSize, const Size& aVideoSize);
nsresult SelectCaptureAndPreviewSize(const Size& aPreviewSize, const Size& aCaptureSize,
const Size& aMaxSize, uint32_t aCaptureSizeKey);
nsresult MaybeAdjustVideoSize();
nsresult PausePreview();
nsresult GetSupportedSize(const Size& aSize, const nsTArray<Size>& supportedSizes, Size& best);
@ -158,7 +160,6 @@ protected:
android::sp<android::GonkCameraHardware> mCameraHw;
Size mLastPictureSize;
Size mLastThumbnailSize;
Size mLastRecorderSize;
uint32_t mPreviewFps;

View File

@ -145,6 +145,7 @@ public:
struct Configuration {
Mode mMode;
Size mPreviewSize;
Size mPictureSize;
nsString mRecorderProfile;
};

View File

@ -13,3 +13,5 @@ support-files = camera_common.js
[test_bug1022766.html]
[test_bug1037322.html]
[test_bug1099390.html]
[test_bug1104913.html]
[test_camera_bad_initial_config.html]

View File

@ -15,7 +15,7 @@
var whichCamera = navigator.mozCameras.getListOfCameras()[0];
var config = {
mode: 'picture',
recorderProfile: 'cif',
recorderProfile: 'high',
previewSize: {
width: 352,
height: 288

View File

@ -15,10 +15,10 @@
var whichCamera = navigator.mozCameras.getListOfCameras()[0];
var config = {
mode: 'picture',
recorderProfile: 'cif',
recorderProfile: 'high',
previewSize: {
width: 352,
height: 288
width: 320,
height: 240
}
};
@ -57,14 +57,17 @@ var Camera = {
ok(cfg.mode === "unspecified", "Initial mode = " + cfg.mode);
ok(cfg.previewSize.width === 0 && cfg.previewSize.height === 0,
"Initial preview size = " + cfg.previewSize.width + "x" + cfg.previewSize.height);
ok(cfg.recorderProfile === "",
ok(cfg.recorderProfile === "default",
"Initial recorder profile = '" + cfg.recorderProfile + "'");
// Apply our specific configuration
camera.setConfiguration(config).then(setConfig_onSuccess, onError);
}
navigator.mozCameras.getCamera(whichCamera, {}).then(getCamera_onSuccess, onError);
var cfg = {
mode: 'unspecified',
};
navigator.mozCameras.getCamera(whichCamera, cfg).then(getCamera_onSuccess, onError);
}
}

View File

@ -15,7 +15,7 @@
var whichCamera = navigator.mozCameras.getListOfCameras()[0];
var config = {
mode: 'picture',
recorderProfile: 'cif',
recorderProfile: 'high',
previewSize: {
width: 352,
height: 288

View File

@ -0,0 +1,81 @@
<!DOCTYPE HTML>
<html>
<head>
<title>Test for bug 1104913</title>
<script type="text/javascript" src="/MochiKit/MochiKit.js"></script>
<script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
<script type="text/javascript" src="camera_common.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
</head>
<body>
<video id="viewfinder" width="200" height="200" autoplay></video>
<img src="#" alt="This image is going to load" id="testimage"/>
<script class="testbody" type="text/javascript;version=1.7">
var whichCamera = navigator.mozCameras.getListOfCameras()[0];
var config = {
mode: 'picture',
recorderProfile: 'qvga',
pictureSize: {
width: 640,
height: 480
},
previewSize: {
width: 320,
height: 240
}
};
function onError(e) {
ok(false, "Error: " + JSON.stringify(e));
}
var Camera = {
cameraObj: null,
get viewfinder() {
return document.getElementById('viewfinder');
},
start: function test_start() {
function getCamera_onSuccess(d) {
var camera = d.camera;
var cfg = d.configuration;
Camera.cameraObj = camera;
Camera.viewfinder.mozSrcObject = camera;
Camera.viewfinder.play();
// Check the default configuration
ok(cfg.mode === config.mode, "Initial mode = " + cfg.mode);
ok(cfg.previewSize.width === config.previewSize.width &&
cfg.previewSize.height === config.previewSize.height,
"Initial preview size = " + cfg.previewSize.width + "x" + cfg.previewSize.height);
ok(cfg.pictureSize.width === config.pictureSize.width &&
cfg.pictureSize.height === config.pictureSize.height,
"Initial picture size = " + cfg.pictureSize.width + "x" + cfg.pictureSize.height);
ok(cfg.recorderProfile === config.recorderProfile,
"Initial recorder profile = '" + cfg.recorderProfile + "'");
SimpleTest.finish();
}
navigator.mozCameras.getCamera(whichCamera, {}).then(getCamera_onSuccess, onError);
}
}
SimpleTest.waitForExplicitFinish();
window.addEventListener('beforeunload', function() {
Camera.viewfinder.mozSrcObject = null;
if (Camera.cameraObj) {
Camera.cameraObj.release();
Camera.cameraObj = null;
}
});
Camera.start();
</script>
</body>
</html>

View File

@ -17,10 +17,10 @@ const Cr = Components.results;
var whichCamera = navigator.mozCameras.getListOfCameras()[0];
var config = {
mode: 'picture',
recorderProfile: 'cif',
recorderProfile: 'high',
previewSize: {
width: 352,
height: 288
width: 320,
height: 240
}
};
var options = {
@ -137,7 +137,7 @@ var tests = [
next();
}
var recordingOptions = {
profile: 'cif',
profile: 'high',
rotation: 0
};
camera.startRecording(recordingOptions,

View File

@ -14,10 +14,10 @@
var whichCamera = navigator.mozCameras.getListOfCameras()[0];
var options = {
mode: 'picture',
recorderProfile: 'cif',
recorderProfile: 'high',
previewSize: {
width: 352,
height: 288
width: 320,
height: 240
}
};

View File

@ -14,10 +14,10 @@
var whichCamera = navigator.mozCameras.getListOfCameras()[0];
var options = {
mode: 'picture',
recorderProfile: 'cif',
recorderProfile: 'high',
previewSize: {
width: 352,
height: 288
width: 320,
height: 240
}
};

View File

@ -14,7 +14,7 @@
var whichCamera = navigator.mozCameras.getListOfCameras()[0];
var options = {
mode: 'picture',
recorderProfile: 'cif',
recorderProfile: 'high',
previewSize: {
width: 352,
height: 288

View File

@ -0,0 +1,58 @@
<!DOCTYPE HTML>
<html>
<head>
<title>Test for bad initial configuration</title>
<script type="text/javascript" src="/MochiKit/MochiKit.js"></script>
<script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
<script type="text/javascript" src="camera_common.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
</head>
<body>
<video id="viewfinder" width="200" height="200" autoplay></video>
<img src="#" alt="This image is going to load" id="testimage"/>
<script class="testbody" type="text/javascript;version=1.7">
var whichCamera = navigator.mozCameras.getListOfCameras()[0];
var config = {
mode: 'picture',
recorderProfile: 'foobar',
};
var Camera = {
cameraObj: null,
get viewfinder() {
return document.getElementById('viewfinder');
},
start: function test_start() {
function getCamera_onSuccess(d) {
ok(false, "Get camera should have failed");
SimpleTest.finish();
}
function getCamera_onError(e) {
ok(true, "Get camera failed as expected: " + JSON.stringify(e));
SimpleTest.finish();
}
navigator.mozCameras.getCamera(whichCamera, config).then(getCamera_onSuccess, getCamera_onError);
}
}
SimpleTest.waitForExplicitFinish();
window.addEventListener('beforeunload', function() {
Camera.viewfinder.mozSrcObject = null;
if (Camera.cameraObj) {
Camera.cameraObj.release();
Camera.cameraObj = null;
}
});
Camera.start();
</script>
</body>
</html>

View File

@ -17,7 +17,7 @@
var whichCamera = navigator.mozCameras.getListOfCameras()[0];
var initialConfig = {
mode: 'picture',
recorderProfile: 'cif',
recorderProfile: 'high',
previewSize: {
width: 352,
height: 288

View File

@ -20,7 +20,7 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=965421
var whichCamera = navigator.mozCameras.getListOfCameras()[0];
var initialConfig = {
mode: 'picture',
recorderProfile: 'cif',
recorderProfile: 'high',
previewSize: {
width: 352,
height: 288

View File

@ -20,7 +20,7 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=965420
var whichCamera = navigator.mozCameras.getListOfCameras()[0];
var initialConfig = {
mode: 'picture',
recorderProfile: 'cif',
recorderProfile: 'high',
previewSize: {
width: 352,
height: 288

View File

@ -20,7 +20,7 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=940424
var whichCamera = navigator.mozCameras.getListOfCameras()[0];
var initialConfig = {
mode: 'picture',
recorderProfile: 'cif',
recorderProfile: 'high',
previewSize: {
width: 352,
height: 288

View File

@ -22,7 +22,7 @@ SimpleTest.waitForExplicitFinish();
var whichCamera = navigator.mozCameras.getListOfCameras()[0];
var initialConfig = {
mode: 'picture',
recorderProfile: 'cif',
recorderProfile: 'high',
previewSize: {
width: 352,
height: 288

View File

@ -39,3 +39,5 @@ LOCAL_INCLUDES += [
'/dom/base',
]
if not CONFIG['CLANG_CXX']:
FAIL_ON_WARNINGS = True

View File

@ -2991,6 +2991,14 @@ void HTMLMediaElement::Error(uint16_t aErrorCode)
aErrorCode == nsIDOMMediaError::MEDIA_ERR_NETWORK ||
aErrorCode == nsIDOMMediaError::MEDIA_ERR_ABORTED,
"Only use nsIDOMMediaError codes!");
// Since we have multiple paths calling into DecodeError, e.g.
// MediaKeys::Terminated and EMEH264Decoder::Error. We should take the 1st
// one only in order not to fire multiple 'error' events.
if (mError) {
return;
}
mError = new MediaError(this, aErrorCode);
DispatchAsyncEvent(NS_LITERAL_STRING("error"));
if (mReadyState == nsIDOMHTMLMediaElement::HAVE_NOTHING) {

View File

@ -104,6 +104,16 @@ size_t MediaDecoderReader::SizeOfAudioQueueInBytes() const
return functor.mSize;
}
size_t MediaDecoderReader::SizeOfVideoQueueInFrames()
{
return mVideoQueue.GetSize();
}
size_t MediaDecoderReader::SizeOfAudioQueueInFrames()
{
return mAudioQueue.GetSize();
}
nsresult MediaDecoderReader::ResetDecode()
{
nsresult res = NS_OK;

View File

@ -188,6 +188,9 @@ public:
// the audio queue.
size_t SizeOfAudioQueueInBytes() const;
virtual size_t SizeOfVideoQueueInFrames();
virtual size_t SizeOfAudioQueueInFrames();
// Only used by WebMReader and MediaOmxReader for now, so stub here rather
// than in every reader than inherits from MediaDecoderReader.
virtual void NotifyDataArrived(const char* aBuffer, uint32_t aLength, int64_t aOffset) {}

View File

@ -623,6 +623,9 @@ MediaDecoderStateMachine::DecodeVideo()
mVideoDecodeStartTime = TimeStamp::Now();
}
SAMPLE_LOG("DecodeVideo() queued=%i, decoder-queued=%o, skip=%i, time=%lld",
VideoQueue().GetSize(), mReader->SizeOfVideoQueueInFrames(), skipToNextKeyFrame, currentTime);
mReader->RequestVideoData(skipToNextKeyFrame, currentTime)
->Then(DecodeTaskQueue(), __func__, this,
&MediaDecoderStateMachine::OnVideoDecoded,
@ -669,6 +672,10 @@ MediaDecoderStateMachine::DecodeAudio()
mIsAudioPrerolling = false;
}
}
SAMPLE_LOG("DecodeAudio() queued=%i, decoder-queued=%o",
AudioQueue().GetSize(), mReader->SizeOfAudioQueueInFrames());
mReader->RequestAudioData()->Then(DecodeTaskQueue(), __func__, this,
&MediaDecoderStateMachine::OnAudioDecoded,
&MediaDecoderStateMachine::OnAudioNotDecoded);
@ -2760,7 +2767,9 @@ void MediaDecoderStateMachine::RenderVideoFrame(VideoData* aData,
return;
}
VERBOSE_LOG("playing video frame %lld", aData->mTime);
VERBOSE_LOG("playing video frame %lld (queued=%i, state-machine=%i, decoder-queued=%i)",
aData->mTime, VideoQueue().GetSize() + mReader->SizeOfVideoQueueInFrames(),
VideoQueue().GetSize(), mReader->SizeOfVideoQueueInFrames());
VideoFrameContainer* container = mDecoder->GetVideoFrameContainer();
if (container) {

View File

@ -26,3 +26,5 @@ LOCAL_INCLUDES += [
]
FINAL_LIBRARY = 'xul'
FAIL_ON_WARNINGS = True

View File

@ -329,8 +329,8 @@ void
CDMCallbackProxy::Terminated()
{
MOZ_ASSERT(mProxy->IsOnGMPThread());
mProxy->gmp_Terminated();
nsRefPtr<nsIRunnable> task = NS_NewRunnableMethod(mProxy, &CDMProxy::Terminated);
NS_DispatchToMainThread(task);
}
} // namespace mozilla

View File

@ -569,11 +569,13 @@ CDMProxy::gmp_Decrypted(uint32_t aId,
}
void
CDMProxy::gmp_Terminated()
CDMProxy::Terminated()
{
MOZ_ASSERT(IsOnGMPThread());
MOZ_ASSERT(NS_IsMainThread());
NS_WARNING("CDM terminated");
gmp_Shutdown();
if (!mKeys.IsNull()) {
mKeys->Terminated();
}
}
} // namespace mozilla

View File

@ -104,6 +104,9 @@ public:
// Main thread only.
void Shutdown();
// Main thread only.
void Terminated();
// Threadsafe.
const nsCString& GetNodeId() const;
@ -157,9 +160,6 @@ public:
GMPErr aResult,
const nsTArray<uint8_t>& aDecryptedData);
// GMP thread only.
void gmp_Terminated();
CDMCaps& Capabilites();
// Main thread only.

View File

@ -66,6 +66,43 @@ MediaKeys::~MediaKeys()
Shutdown();
}
static PLDHashOperator
CopySessions(const nsAString& aKey,
nsRefPtr<MediaKeySession>& aSession,
void* aClosure)
{
KeySessionHashMap* p = static_cast<KeySessionHashMap*>(aClosure);
p->Put(aSession->GetSessionId(), aSession);
return PL_DHASH_NEXT;
}
static PLDHashOperator
CloseSessions(const nsAString& aKey,
nsRefPtr<MediaKeySession>& aSession,
void* aClosure)
{
aSession->OnClosed();
return PL_DHASH_NEXT;
}
void
MediaKeys::Terminated()
{
KeySessionHashMap keySessions;
// Remove entries during iteration will screw it. Make a copy first.
mKeySessions.Enumerate(&CopySessions, &keySessions);
keySessions.Enumerate(&CloseSessions, nullptr);
keySessions.Clear();
MOZ_ASSERT(mKeySessions.Count() == 0);
// Notify the element about that CDM has terminated.
if (mElement) {
mElement->DecodeError();
}
Shutdown();
}
void
MediaKeys::Shutdown()
{

View File

@ -104,6 +104,10 @@ public:
void Shutdown();
// Called by CDMProxy when CDM crashes or shuts down. It is different from
// Shutdown which is called from the script/dom side.
void Terminated();
// Returns true if this MediaKeys has been bound to a media element.
bool IsBoundToMediaElement() const;

View File

@ -647,6 +647,26 @@ MP4Reader::PopSample(TrackType aTrack)
}
}
size_t
MP4Reader::SizeOfVideoQueueInFrames()
{
return SizeOfQueue(kVideo);
}
size_t
MP4Reader::SizeOfAudioQueueInFrames()
{
return SizeOfQueue(kAudio);
}
size_t
MP4Reader::SizeOfQueue(TrackType aTrack)
{
auto& decoder = GetDecoderData(aTrack);
MonitorAutoLock lock(decoder.mMonitor);
return decoder.mOutput.Length() + (decoder.mNumSamplesInput - decoder.mNumSamplesOutput);
}
nsresult
MP4Reader::ResetDecode()
{

View File

@ -37,6 +37,9 @@ public:
virtual nsresult Init(MediaDecoderReader* aCloneDonor) MOZ_OVERRIDE;
virtual size_t SizeOfVideoQueueInFrames() MOZ_OVERRIDE;
virtual size_t SizeOfAudioQueueInFrames() MOZ_OVERRIDE;
virtual nsRefPtr<VideoDataPromise>
RequestVideoData(bool aSkipToNextKeyframe, int64_t aTimeThreshold) MOZ_OVERRIDE;
@ -117,6 +120,8 @@ private:
bool IsWaitingOnCodecResource();
virtual bool IsWaitingOnCDMResource() MOZ_OVERRIDE;
size_t SizeOfQueue(TrackType aTrack);
nsAutoPtr<mp4_demuxer::MP4Demuxer> mDemuxer;
nsAutoPtr<PlatformDecoderModule> mPlatform;

View File

@ -93,6 +93,26 @@ MediaSourceReader::IsWaitingMediaResources()
return !mHasEssentialTrackBuffers;
}
size_t
MediaSourceReader::SizeOfVideoQueueInFrames()
{
if (!mVideoReader) {
MSE_DEBUG("MediaSourceReader(%p)::SizeOfVideoQueue called with no video reader", this);
return 0;
}
return mVideoReader->SizeOfVideoQueueInFrames();
}
size_t
MediaSourceReader::SizeOfAudioQueueInFrames()
{
if (!mAudioReader) {
MSE_DEBUG("MediaSourceReader(%p)::SizeOfAudioQueue called with no audio reader", this);
return 0;
}
return mAudioReader->SizeOfAudioQueueInFrames();
}
nsRefPtr<MediaDecoderReader::AudioDataPromise>
MediaSourceReader::RequestAudioData()
{

View File

@ -50,6 +50,9 @@ public:
nsRefPtr<VideoDataPromise>
RequestVideoData(bool aSkipToNextKeyframe, int64_t aTimeThreshold) MOZ_OVERRIDE;
virtual size_t SizeOfVideoQueueInFrames() MOZ_OVERRIDE;
virtual size_t SizeOfAudioQueueInFrames() MOZ_OVERRIDE;
void OnAudioDecoded(AudioData* aSample);
void OnAudioNotDecoded(NotDecodedReason aReason);
void OnVideoDecoded(VideoData* aSample);

View File

@ -14,6 +14,7 @@
#include "SourceBufferResource.h"
#include "VideoUtils.h"
#include "mozilla/dom/TimeRanges.h"
#include "mozilla/Preferences.h"
#include "nsError.h"
#include "nsIRunnable.h"
#include "nsThreadUtils.h"
@ -43,6 +44,7 @@ TrackBuffer::TrackBuffer(MediaSourceDecoder* aParentDecoder, const nsACString& a
mParser = ContainerParser::CreateForMIMEType(aType);
mTaskQueue = new MediaTaskQueue(GetMediaDecodeThreadPool());
aParentDecoder->AddTrackBuffer(this);
mDecoderPerSegment = Preferences::GetBool("media.mediasource.decoder-per-segment", false);
}
TrackBuffer::~TrackBuffer()
@ -150,7 +152,8 @@ TrackBuffer::AppendData(const uint8_t* aData, uint32_t aLength)
if (mParser->ParseStartAndEndTimestamps(aData, aLength, start, end)) {
if (mParser->IsMediaSegmentPresent(aData, aLength) &&
mLastEndTimestamp &&
!mParser->TimestampsFuzzyEqual(start, mLastEndTimestamp.value())) {
(!mParser->TimestampsFuzzyEqual(start, mLastEndTimestamp.value()) ||
mDecoderPerSegment)) {
MSE_DEBUG("TrackBuffer(%p)::AppendData: Data last=[%lld, %lld] overlaps [%lld, %lld]",
this, mLastStartTimestamp, mLastEndTimestamp.value(), start, end);

View File

@ -162,6 +162,7 @@ private:
void ContinueShutdown();
MediaPromiseHolder<ShutdownPromise> mShutdownPromise;
bool mDecoderPerSegment;
};
} // namespace mozilla

View File

@ -36,3 +36,5 @@ CXXFLAGS += [
]
FINAL_LIBRARY = 'xul'
FAIL_ON_WARNINGS = True

Some files were not shown because too many files have changed in this diff Show More