mirror of
https://github.com/mozilla/gecko-dev.git
synced 2025-02-23 10:54:33 +00:00
Implement CSS transitions: handle starting and animation of the transitions. (Bug 435441) r=bzbarsky sr=roc
This commit is contained in:
parent
4c250ab973
commit
2ccbe80f89
@ -2691,6 +2691,16 @@ nsGenericElement::UnbindFromTree(PRBool aDeep, PRBool aNullParent)
|
||||
document->ClearBoxObjectFor(this);
|
||||
}
|
||||
|
||||
// Ensure that CSS transitions don't continue on an element at a
|
||||
// different place in the tree (even if reinserted before next
|
||||
// animation refresh).
|
||||
// FIXME: Need a test for this.
|
||||
if (HasFlag(NODE_HAS_PROPERTIES)) {
|
||||
DeleteProperty(nsGkAtoms::transitionsOfBeforeProperty);
|
||||
DeleteProperty(nsGkAtoms::transitionsOfAfterProperty);
|
||||
DeleteProperty(nsGkAtoms::transitionsProperty);
|
||||
}
|
||||
|
||||
// Unset this since that's what the old code effectively did.
|
||||
UnsetFlags(NODE_FORCE_XBL_BINDINGS);
|
||||
|
||||
|
@ -1674,6 +1674,9 @@ GK_ATOM(preTransformBBoxProperty, "PreTransformBBoxProperty") // nsRect*
|
||||
GK_ATOM(rowUnpaginatedHeightProperty, "RowUnpaginatedHeightProperty") // nscoord*
|
||||
GK_ATOM(tabWidthProperty, "TabWidthProperty") // nsTArray<TabSetting>* array of tab widths
|
||||
GK_ATOM(tableBCProperty, "TableBCProperty") // table border collapsing info (e.g. damage area, table border widths)
|
||||
GK_ATOM(transitionsProperty, "TransitionsProperty") // FrameTransitions*
|
||||
GK_ATOM(transitionsOfBeforeProperty, "TransitionsOfBeforeProperty") // FrameTransitions*
|
||||
GK_ATOM(transitionsOfAfterProperty, "TransitionsOfAfterProperty") // FrameTransitions*
|
||||
GK_ATOM(usedMarginProperty, "UsedMarginProperty") // nsMargin*
|
||||
GK_ATOM(usedPaddingProperty, "UsedPaddingProperty") // nsMargin*
|
||||
GK_ATOM(viewProperty, "ViewProperty")
|
||||
|
@ -81,6 +81,7 @@ EXPORTS = \
|
||||
nsISMILAttr.h \
|
||||
nsSMILAnimationController.h \
|
||||
nsSMILCompositorTable.h \
|
||||
nsSMILKeySpline.h \
|
||||
nsSMILTimeContainer.h \
|
||||
nsSMILTypes.h \
|
||||
$(NULL)
|
||||
|
@ -47,15 +47,17 @@
|
||||
const double nsSMILKeySpline::kSampleStepSize =
|
||||
1.0 / double(kSplineTableSize - 1);
|
||||
|
||||
nsSMILKeySpline::nsSMILKeySpline(double aX1,
|
||||
double aY1,
|
||||
double aX2,
|
||||
double aY2)
|
||||
: mX1(aX1),
|
||||
mY1(aY1),
|
||||
mX2(aX2),
|
||||
mY2(aY2)
|
||||
void
|
||||
nsSMILKeySpline::Init(double aX1,
|
||||
double aY1,
|
||||
double aX2,
|
||||
double aY2)
|
||||
{
|
||||
mX1 = aX1;
|
||||
mY1 = aY1;
|
||||
mX2 = aX2;
|
||||
mY2 = aY2;
|
||||
|
||||
if (mX1 != mY1 || mX2 != mY2)
|
||||
CalcSampleValues();
|
||||
}
|
||||
|
@ -44,6 +44,8 @@
|
||||
class nsSMILKeySpline
|
||||
{
|
||||
public:
|
||||
nsSMILKeySpline() { /* caller must call Init later */ }
|
||||
|
||||
/**
|
||||
* Creates a new key spline control point description.
|
||||
*
|
||||
@ -51,7 +53,13 @@ public:
|
||||
* SMILANIM 3.2.3. They must each be in the range 0.0 <= x <= 1.0
|
||||
*/
|
||||
nsSMILKeySpline(double aX1, double aY1,
|
||||
double aX2, double aY2);
|
||||
double aX2, double aY2)
|
||||
{
|
||||
Init(aX1, aY1, aX2, aY2);
|
||||
}
|
||||
|
||||
void Init(double aX1, double aY1,
|
||||
double aX2, double aY2);
|
||||
|
||||
/**
|
||||
* Gets the output (y) value for an input (x).
|
||||
@ -104,10 +112,10 @@ private:
|
||||
return 3.0 * aA1;
|
||||
}
|
||||
|
||||
const double mX1;
|
||||
const double mY1;
|
||||
const double mX2;
|
||||
const double mY2;
|
||||
double mX1;
|
||||
double mY1;
|
||||
double mX2;
|
||||
double mY2;
|
||||
|
||||
enum { kSplineTableSize = 11 };
|
||||
double mSampleValues[kSplineTableSize];
|
||||
|
@ -81,6 +81,7 @@ EXPORTS = \
|
||||
nsLayoutUtils.h \
|
||||
nsPresContext.h \
|
||||
nsPresState.h \
|
||||
nsRefreshDriver.h \
|
||||
nsStyleChangeList.h \
|
||||
nsStyleConsts.h \
|
||||
$(NULL)
|
||||
@ -107,6 +108,7 @@ CPPSRCS = \
|
||||
nsPresShell.cpp \
|
||||
nsPresState.cpp \
|
||||
nsQuoteList.cpp \
|
||||
nsRefreshDriver.cpp \
|
||||
nsStyleChangeList.cpp \
|
||||
nsStyleSheetService.cpp \
|
||||
$(NULL)
|
||||
|
@ -68,6 +68,7 @@ class nsIFrame;
|
||||
struct nsGenConInitializer;
|
||||
class ChildIterator;
|
||||
class nsICSSAnonBoxPseudo;
|
||||
class nsPageContentFrame;
|
||||
|
||||
struct nsFindFrameHint
|
||||
{
|
||||
|
@ -90,6 +90,7 @@
|
||||
#include "nsLayoutUtils.h"
|
||||
#include "nsAutoPtr.h"
|
||||
#include "imgIRequest.h"
|
||||
#include "nsTransitionManager.h"
|
||||
|
||||
#include "nsFrameManager.h"
|
||||
#ifdef ACCESSIBILITY
|
||||
@ -926,6 +927,33 @@ nsFrameManager::DebugVerifyStyleTree(nsIFrame* aFrame)
|
||||
|
||||
#endif // DEBUG
|
||||
|
||||
// aContent must be the content for the frame in question, which may be
|
||||
// :before/:after content
|
||||
static void
|
||||
TryStartingTransition(nsPresContext *aPresContext, nsIContent *aContent,
|
||||
nsStyleContext *aOldStyleContext,
|
||||
nsRefPtr<nsStyleContext> *aNewStyleContext /* inout */)
|
||||
{
|
||||
// Notify the transition manager, and if it starts a transition,
|
||||
// it will give us back a transition-covering style rule which
|
||||
// we'll use to get *another* style context. We want to ignore
|
||||
// any already-running transitions, but cover up any that we're
|
||||
// currently starting with their start value so we don't start
|
||||
// them again for descendants that inherit that value.
|
||||
nsCOMPtr<nsIStyleRule> coverRule =
|
||||
aPresContext->TransitionManager()->StyleContextChanged(
|
||||
aContent, aOldStyleContext, *aNewStyleContext);
|
||||
if (coverRule) {
|
||||
nsCOMArray<nsIStyleRule> rules;
|
||||
rules.AppendObject(coverRule);
|
||||
*aNewStyleContext = aPresContext->StyleSet()->ResolveStyleForRules(
|
||||
(*aNewStyleContext)->GetParent(),
|
||||
(*aNewStyleContext)->GetPseudoType(),
|
||||
(*aNewStyleContext)->GetRuleNode(),
|
||||
rules);
|
||||
}
|
||||
}
|
||||
|
||||
nsresult
|
||||
nsFrameManager::ReParentStyleContext(nsIFrame* aFrame)
|
||||
{
|
||||
@ -971,6 +999,16 @@ nsFrameManager::ReParentStyleContext(nsIFrame* aFrame)
|
||||
newParentContext);
|
||||
if (newContext) {
|
||||
if (newContext != oldContext) {
|
||||
// We probably don't want to initiate transitions from
|
||||
// ReParentStyleContext, since we call it during frame
|
||||
// construction rather than in response to dynamic changes.
|
||||
// Also see the comment at the start of
|
||||
// nsTransitionManager::ConsiderStartingTransition.
|
||||
#if 0
|
||||
TryStartingTransition(presContext, aFrame->GetContent(),
|
||||
oldContext, &newContext);
|
||||
#endif
|
||||
|
||||
// Make sure to call CalcStyleDifference so that the new context ends
|
||||
// up resolving all the structs the old context resolved.
|
||||
nsChangeHint styleChange = oldContext->CalcStyleDifference(newContext);
|
||||
@ -1260,6 +1298,9 @@ nsFrameManager::ReResolveStyleContext(nsPresContext *aPresContext,
|
||||
}
|
||||
|
||||
if (newContext != oldContext) {
|
||||
TryStartingTransition(aPresContext, aFrame->GetContent(),
|
||||
oldContext, &newContext);
|
||||
|
||||
aMinChange = CaptureChange(oldContext, newContext, aFrame,
|
||||
content, aChangeList, aMinChange,
|
||||
assumeDifferenceHint);
|
||||
|
@ -94,6 +94,7 @@
|
||||
#include "nsIPrivateDOMEvent.h"
|
||||
#include "nsIDOMEventTarget.h"
|
||||
#include "nsObjectFrame.h"
|
||||
#include "nsTransitionManager.h"
|
||||
|
||||
#ifdef MOZ_SMIL
|
||||
#include "nsSMILAnimationController.h"
|
||||
@ -258,6 +259,8 @@ nsPresContext::~nsPresContext()
|
||||
NS_PRECONDITION(!mShell, "Presshell forgot to clear our mShell pointer");
|
||||
SetShell(nsnull);
|
||||
|
||||
delete mTransitionManager;
|
||||
|
||||
if (mEventManager) {
|
||||
// unclear if these are needed, but can't hurt
|
||||
mEventManager->NotifyDestroyPresContext(this);
|
||||
@ -869,6 +872,8 @@ nsPresContext::Init(nsIDeviceContext* aDeviceContext)
|
||||
|
||||
NS_ADDREF(mEventManager);
|
||||
|
||||
mTransitionManager = new nsTransitionManager(this);
|
||||
|
||||
mLangService = do_GetService(NS_LANGUAGEATOMSERVICE_CONTRACTID);
|
||||
|
||||
// Register callbacks so we're notified when the preferences change
|
||||
|
@ -72,6 +72,7 @@
|
||||
#include "nsContentUtils.h"
|
||||
#include "nsIWidget.h"
|
||||
#include "mozilla/TimeStamp.h"
|
||||
#include "nsRefreshDriver.h"
|
||||
|
||||
class nsImageLoader;
|
||||
#ifdef IBMBIDI
|
||||
@ -101,6 +102,7 @@ class gfxUserFontSet;
|
||||
class nsUserFontSet;
|
||||
struct nsFontFaceRuleContainer;
|
||||
class nsObjectFrame;
|
||||
class nsTransitionManager;
|
||||
|
||||
#ifdef MOZ_REFLOW_PERF
|
||||
class nsIRenderingContext;
|
||||
@ -227,6 +229,16 @@ public:
|
||||
|
||||
nsFrameManager* FrameManager()
|
||||
{ return GetPresShell()->FrameManager(); }
|
||||
|
||||
nsTransitionManager* TransitionManager() { return mTransitionManager; }
|
||||
|
||||
nsRefreshDriver* RefreshDriver() { return &mRefreshDriver; }
|
||||
|
||||
static nsPresContext* FromRefreshDriver(nsRefreshDriver* aRefreshDriver) {
|
||||
return reinterpret_cast<nsPresContext*>(
|
||||
reinterpret_cast<char*>(aRefreshDriver) -
|
||||
offsetof(nsPresContext, mRefreshDriver));
|
||||
}
|
||||
#endif
|
||||
|
||||
/**
|
||||
@ -945,6 +957,8 @@ protected:
|
||||
// from gfx back to layout.
|
||||
nsIEventStateManager* mEventManager; // [STRONG]
|
||||
nsILookAndFeel* mLookAndFeel; // [STRONG]
|
||||
nsRefreshDriver mRefreshDriver;
|
||||
nsTransitionManager* mTransitionManager; // owns; it aggregates our refcount
|
||||
nsIAtom* mMedium; // initialized by subclass ctors;
|
||||
// weak pointer to static atom
|
||||
|
||||
|
219
layout/base/nsRefreshDriver.cpp
Normal file
219
layout/base/nsRefreshDriver.cpp
Normal file
@ -0,0 +1,219 @@
|
||||
/* vim: set shiftwidth=2 tabstop=8 autoindent cindent expandtab: */
|
||||
/* ***** BEGIN LICENSE BLOCK *****
|
||||
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
|
||||
*
|
||||
* The contents of this file are subject to the Mozilla Public License Version
|
||||
* 1.1 (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
* http://www.mozilla.org/MPL/
|
||||
*
|
||||
* Software distributed under the License is distributed on an "AS IS" basis,
|
||||
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
|
||||
* for the specific language governing rights and limitations under the
|
||||
* License.
|
||||
*
|
||||
* The Original Code is nsRefreshDriver.
|
||||
*
|
||||
* The Initial Developer of the Original Code is the Mozilla Foundation.
|
||||
* Portions created by the Initial Developer are Copyright (C) 2009
|
||||
* the Initial Developer. All Rights Reserved.
|
||||
*
|
||||
* Contributor(s):
|
||||
* L. David Baron <dbaron@dbaron.org>, Mozilla Corporation (original author)
|
||||
*
|
||||
* Alternatively, the contents of this file may be used under the terms of
|
||||
* either the GNU General Public License Version 2 or later (the "GPL"), or
|
||||
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
|
||||
* in which case the provisions of the GPL or the LGPL are applicable instead
|
||||
* of those above. If you wish to allow use of your version of this file only
|
||||
* under the terms of either the GPL or the LGPL, and not to allow others to
|
||||
* use your version of this file under the terms of the MPL, indicate your
|
||||
* decision by deleting the provisions above and replace them with the notice
|
||||
* and other provisions required by the GPL or the LGPL. If you do not delete
|
||||
* the provisions above, a recipient may use your version of this file under
|
||||
* the terms of any one of the MPL, the GPL or the LGPL.
|
||||
*
|
||||
* ***** END LICENSE BLOCK ***** */
|
||||
|
||||
/*
|
||||
* Code to notify things that animate before a refresh, at an appropriate
|
||||
* refresh rate. (Perhaps temporary, until replaced by compositor.)
|
||||
*/
|
||||
|
||||
#include "nsRefreshDriver.h"
|
||||
#include "nsPresContext.h"
|
||||
#include "nsComponentManagerUtils.h"
|
||||
#include "prlog.h"
|
||||
|
||||
/*
|
||||
* TODO:
|
||||
* Once this is hooked in to suppressing updates when the presentation
|
||||
* is not visible, we need to hook it up to FlushPendingNotifications so
|
||||
* that we flush when necessary.
|
||||
*/
|
||||
|
||||
#define REFRESH_INTERVAL_MILLISECONDS 20
|
||||
|
||||
using mozilla::TimeStamp;
|
||||
|
||||
nsRefreshDriver::nsRefreshDriver()
|
||||
{
|
||||
}
|
||||
|
||||
nsRefreshDriver::~nsRefreshDriver()
|
||||
{
|
||||
NS_ABORT_IF_FALSE(ObserverCount() == 0,
|
||||
"observers should have unregistered");
|
||||
NS_ABORT_IF_FALSE(!mTimer, "timer should be gone");
|
||||
}
|
||||
|
||||
TimeStamp
|
||||
nsRefreshDriver::MostRecentRefresh() const
|
||||
{
|
||||
const_cast<nsRefreshDriver*>(this)->EnsureTimerStarted();
|
||||
|
||||
return mMostRecentRefresh;
|
||||
}
|
||||
|
||||
PRBool
|
||||
nsRefreshDriver::AddRefreshObserver(nsARefreshObserver *aObserver,
|
||||
mozFlushType aFlushType)
|
||||
{
|
||||
ObserverArray& array = ArrayFor(aFlushType);
|
||||
PRBool success = array.AppendElement(aObserver) != nsnull;
|
||||
|
||||
EnsureTimerStarted();
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
PRBool
|
||||
nsRefreshDriver::RemoveRefreshObserver(nsARefreshObserver *aObserver,
|
||||
mozFlushType aFlushType)
|
||||
{
|
||||
ObserverArray& array = ArrayFor(aFlushType);
|
||||
PRBool success = array.RemoveElement(aObserver);
|
||||
|
||||
if (ObserverCount() == 0) {
|
||||
StopTimer();
|
||||
}
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
void
|
||||
nsRefreshDriver::EnsureTimerStarted()
|
||||
{
|
||||
if (mTimer) {
|
||||
// It's already been started.
|
||||
return;
|
||||
}
|
||||
|
||||
UpdateMostRecentRefresh();
|
||||
|
||||
mTimer = do_CreateInstance(NS_TIMER_CONTRACTID);
|
||||
if (!mTimer) {
|
||||
return;
|
||||
}
|
||||
|
||||
nsresult rv = mTimer->InitWithCallback(this, REFRESH_INTERVAL_MILLISECONDS,
|
||||
nsITimer::TYPE_REPEATING_SLACK);
|
||||
if (NS_FAILED(rv)) {
|
||||
mTimer = nsnull;
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
nsRefreshDriver::StopTimer()
|
||||
{
|
||||
if (!mTimer) {
|
||||
return;
|
||||
}
|
||||
|
||||
mTimer->Cancel();
|
||||
mTimer = nsnull;
|
||||
}
|
||||
|
||||
PRUint32
|
||||
nsRefreshDriver::ObserverCount() const
|
||||
{
|
||||
PRUint32 sum = 0;
|
||||
for (PRUint32 i = 0; i < NS_ARRAY_LENGTH(mObservers); ++i) {
|
||||
sum += mObservers[i].Length();
|
||||
}
|
||||
return sum;
|
||||
}
|
||||
|
||||
void
|
||||
nsRefreshDriver::UpdateMostRecentRefresh()
|
||||
{
|
||||
mMostRecentRefresh = TimeStamp::Now();
|
||||
}
|
||||
|
||||
nsRefreshDriver::ObserverArray&
|
||||
nsRefreshDriver::ArrayFor(mozFlushType aFlushType)
|
||||
{
|
||||
switch (aFlushType) {
|
||||
case Flush_Style:
|
||||
return mObservers[0];
|
||||
case Flush_Layout:
|
||||
return mObservers[1];
|
||||
case Flush_Display:
|
||||
return mObservers[2];
|
||||
default:
|
||||
NS_ABORT_IF_FALSE(PR_FALSE, "bad flush type");
|
||||
return *static_cast<ObserverArray*>(nsnull);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* nsISupports implementation
|
||||
*/
|
||||
|
||||
NS_IMPL_ADDREF_USING_AGGREGATOR(nsRefreshDriver,
|
||||
nsPresContext::FromRefreshDriver(this))
|
||||
NS_IMPL_RELEASE_USING_AGGREGATOR(nsRefreshDriver,
|
||||
nsPresContext::FromRefreshDriver(this))
|
||||
NS_IMPL_QUERY_INTERFACE1(nsRefreshDriver, nsITimerCallback)
|
||||
|
||||
/*
|
||||
* nsITimerCallback implementation
|
||||
*/
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsRefreshDriver::Notify(nsITimer *aTimer)
|
||||
{
|
||||
UpdateMostRecentRefresh();
|
||||
|
||||
nsPresContext *presContext = nsPresContext::FromRefreshDriver(this);
|
||||
nsCOMPtr<nsIPresShell> presShell = presContext->GetPresShell();
|
||||
if (!presShell) {
|
||||
// Things are being destroyed.
|
||||
StopTimer();
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
for (PRUint32 i = 0; i < NS_ARRAY_LENGTH(mObservers); ++i) {
|
||||
ObserverArray::EndLimitedIterator etor(mObservers[i]);
|
||||
while (etor.HasMore()) {
|
||||
etor.GetNext()->WillRefresh(mMostRecentRefresh);
|
||||
}
|
||||
if (i == 0) {
|
||||
// This is the Flush_Style case.
|
||||
// FIXME: Maybe we should only flush if the WillRefresh calls did
|
||||
// something? It's probably ok as-is, though, especially as we
|
||||
// hook up more things here (or to the replacement of this class).
|
||||
// FIXME: We should probably flush for other sets of observers
|
||||
// too. But we should only flush layout once nsRefreshDriver is
|
||||
// the driver for the interruptible layout timer (and we should
|
||||
// then Flush_InterruptibleLayout).
|
||||
presShell->FlushPendingNotifications(Flush_Style);
|
||||
}
|
||||
}
|
||||
|
||||
if (ObserverCount() == 0) {
|
||||
StopTimer();
|
||||
}
|
||||
|
||||
return NS_OK;
|
||||
}
|
120
layout/base/nsRefreshDriver.h
Normal file
120
layout/base/nsRefreshDriver.h
Normal file
@ -0,0 +1,120 @@
|
||||
/* vim: set shiftwidth=2 tabstop=8 autoindent cindent expandtab: */
|
||||
/* ***** BEGIN LICENSE BLOCK *****
|
||||
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
|
||||
*
|
||||
* The contents of this file are subject to the Mozilla Public License Version
|
||||
* 1.1 (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
* http://www.mozilla.org/MPL/
|
||||
*
|
||||
* Software distributed under the License is distributed on an "AS IS" basis,
|
||||
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
|
||||
* for the specific language governing rights and limitations under the
|
||||
* License.
|
||||
*
|
||||
* The Original Code is nsRefreshDriver.
|
||||
*
|
||||
* The Initial Developer of the Original Code is the Mozilla Foundation.
|
||||
* Portions created by the Initial Developer are Copyright (C) 2009
|
||||
* the Initial Developer. All Rights Reserved.
|
||||
*
|
||||
* Contributor(s):
|
||||
* L. David Baron <dbaron@dbaron.org>, Mozilla Corporation (original author)
|
||||
*
|
||||
* Alternatively, the contents of this file may be used under the terms of
|
||||
* either the GNU General Public License Version 2 or later (the "GPL"), or
|
||||
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
|
||||
* in which case the provisions of the GPL or the LGPL are applicable instead
|
||||
* of those above. If you wish to allow use of your version of this file only
|
||||
* under the terms of either the GPL or the LGPL, and not to allow others to
|
||||
* use your version of this file under the terms of the MPL, indicate your
|
||||
* decision by deleting the provisions above and replace them with the notice
|
||||
* and other provisions required by the GPL or the LGPL. If you do not delete
|
||||
* the provisions above, a recipient may use your version of this file under
|
||||
* the terms of any one of the MPL, the GPL or the LGPL.
|
||||
*
|
||||
* ***** END LICENSE BLOCK ***** */
|
||||
|
||||
/*
|
||||
* Code to notify things that animate before a refresh, at an appropriate
|
||||
* refresh rate. (Perhaps temporary, until replaced by compositor.)
|
||||
*/
|
||||
|
||||
#ifndef nsRefreshDriver_h_
|
||||
#define nsRefreshDriver_h_
|
||||
|
||||
#include "mozilla/TimeStamp.h"
|
||||
#include "mozFlushType.h"
|
||||
#include "nsITimer.h"
|
||||
#include "nsCOMPtr.h"
|
||||
#include "nsTObserverArray.h"
|
||||
|
||||
/**
|
||||
* An abstract base class to be implemented by callers wanting to be
|
||||
* notified at refresh times. When nothing needs to be painted, callers
|
||||
* may not be notified.
|
||||
*/
|
||||
class nsARefreshObserver {
|
||||
public:
|
||||
virtual void WillRefresh(mozilla::TimeStamp aTime) = 0;
|
||||
};
|
||||
|
||||
/*
|
||||
* nsRefreshDriver MUST ONLY be constructed as a sub-object of
|
||||
* nsPresContext (since its reference counting methods forward to the
|
||||
* pres context of which it is an mRefreshDriver)
|
||||
*/
|
||||
class nsRefreshDriver : private nsITimerCallback {
|
||||
public:
|
||||
nsRefreshDriver();
|
||||
~nsRefreshDriver();
|
||||
|
||||
/**
|
||||
* Return the time of the most recent refresh. This is intended to be
|
||||
* used by callers who want to start an animation now and want to know
|
||||
* what time to consider the start of the animation. (This helps
|
||||
* ensure that multiple animations started during the same event off
|
||||
* the main event loop have the same start time.)
|
||||
*/
|
||||
mozilla::TimeStamp MostRecentRefresh() const;
|
||||
|
||||
/**
|
||||
* Add / remove refresh observers. Returns whether the operation
|
||||
* succeeded.
|
||||
*
|
||||
* The flush type affects:
|
||||
* + the order in which the observers are notified (lowest flush
|
||||
* type to highest, in order registered)
|
||||
* + (in the future) which observers are suppressed when the display
|
||||
* doesn't require current position data or isn't currently
|
||||
* painting, and, correspondingly, which get notified when there
|
||||
* is a flush during such suppression
|
||||
* and it must be either Flush_Style, Flush_Layout, or Flush_Display.
|
||||
*/
|
||||
PRBool AddRefreshObserver(nsARefreshObserver *aObserver,
|
||||
mozFlushType aFlushType);
|
||||
PRBool RemoveRefreshObserver(nsARefreshObserver *aObserver,
|
||||
mozFlushType aFlushType);
|
||||
private:
|
||||
// nsISupports implementation
|
||||
NS_DECL_ISUPPORTS_INHERITED
|
||||
|
||||
// nsITimerCallback implementation
|
||||
NS_IMETHOD Notify(nsITimer *aTimer);
|
||||
|
||||
typedef nsTObserverArray<nsARefreshObserver*> ObserverArray;
|
||||
|
||||
void EnsureTimerStarted();
|
||||
void StopTimer();
|
||||
PRUint32 ObserverCount() const;
|
||||
void UpdateMostRecentRefresh();
|
||||
ObserverArray& ArrayFor(mozFlushType aFlushType);
|
||||
|
||||
nsCOMPtr<nsITimer> mTimer;
|
||||
mozilla::TimeStamp mMostRecentRefresh; // only valid when mTimer non-null
|
||||
|
||||
// separate arrays for each flush type we support
|
||||
ObserverArray mObservers[3];
|
||||
};
|
||||
|
||||
#endif /* !defined(nsRefreshDriver_h_) */
|
4
layout/reftests/css-transitions/reftest.list
Normal file
4
layout/reftests/css-transitions/reftest.list
Normal file
@ -0,0 +1,4 @@
|
||||
== transitions-inline-already-wrapped-1.html transitions-inline-ref.html
|
||||
== transitions-inline-already-wrapped-2.html transitions-inline-ref.html
|
||||
== transitions-inline-rewrap-1.html transitions-inline-ref.html
|
||||
== transitions-inline-rewrap-2.html transitions-inline-ref.html
|
@ -0,0 +1,30 @@
|
||||
<html class="reftest-wait">
|
||||
<title>Test for CSS transitions and re-wrapping of inlines</title>
|
||||
<style type="text/css">
|
||||
#test { -moz-transition: 5s color linear 200s; }
|
||||
</style>
|
||||
<script type="text/javascript">
|
||||
|
||||
window.onload = run;
|
||||
|
||||
function run() {
|
||||
var test = document.getElementById("test");
|
||||
var unused = test.offsetWidth;
|
||||
// FIXME: It's a bug that we need to do this at all: the way we change
|
||||
// style data essentially violates style rule immutability because we
|
||||
// assume that all of the difference calculation will use
|
||||
// PeekStyleData, which is no longer true with transitions.
|
||||
// See the FIXME in nsTransitionManager::ConsiderStartingTransition.
|
||||
unused = getComputedStyle(test, "").color;
|
||||
test.style.color = "red";
|
||||
unused = test.offsetWidth;
|
||||
unused = getComputedStyle(test, "").color;
|
||||
document.documentElement.removeAttribute("class");
|
||||
}
|
||||
|
||||
</script>
|
||||
<div style="width: 3em">
|
||||
<span id="test" style="color: green">
|
||||
This is some text with a transition.
|
||||
</span>
|
||||
</div>
|
@ -0,0 +1,28 @@
|
||||
<html class="reftest-wait">
|
||||
<title>Test for CSS transitions and re-wrapping of inlines</title>
|
||||
<style type="text/css">
|
||||
#test { -moz-transition: 20ms color linear 0; }
|
||||
</style>
|
||||
<script type="text/javascript">
|
||||
|
||||
window.onload = run;
|
||||
|
||||
function run() {
|
||||
var test = document.getElementById("test");
|
||||
var unused = test.offsetWidth;
|
||||
unused = getComputedStyle(test, "").color;
|
||||
test.style.color = "green";
|
||||
unused = test.offsetWidth;
|
||||
unused = getComputedStyle(test, "").color;
|
||||
setTimeout(step2, 100); // give transition time to run
|
||||
}
|
||||
function step2() {
|
||||
document.documentElement.removeAttribute("class");
|
||||
}
|
||||
|
||||
</script>
|
||||
<div style="width: 3em">
|
||||
<span id="test" style="color: red">
|
||||
This is some text with a transition.
|
||||
</span>
|
||||
</div>
|
@ -0,0 +1,7 @@
|
||||
<html>
|
||||
<title>Test for CSS transitions and re-wrapping of inlines</title>
|
||||
<div style="width: 3em">
|
||||
<span id="test" style="color: green">
|
||||
This is some text with a transition.
|
||||
</span>
|
||||
</div>
|
@ -0,0 +1,33 @@
|
||||
<html class="reftest-wait">
|
||||
<title>Test for CSS transitions and re-wrapping of inlines</title>
|
||||
<style type="text/css">
|
||||
#test { -moz-transition: 5s color linear 200s; }
|
||||
</style>
|
||||
<script type="text/javascript">
|
||||
|
||||
window.onload = run;
|
||||
|
||||
function run() {
|
||||
var test = document.getElementById("test");
|
||||
var unused = test.offsetWidth;
|
||||
// FIXME: It's a bug that we need to do this at all: the way we change
|
||||
// style data essentially violates style rule immutability because we
|
||||
// assume that all of the difference calculation will use
|
||||
// PeekStyleData, which is no longer true with transitions.
|
||||
// See the FIXME in nsTransitionManager::ConsiderStartingTransition.
|
||||
unused = getComputedStyle(test, "").color;
|
||||
test.style.color = "red";
|
||||
unused = test.offsetWidth;
|
||||
unused = getComputedStyle(test, "").color;
|
||||
test.parentNode.style.width = "3em";
|
||||
unused = test.offsetWidth;
|
||||
unused = getComputedStyle(test, "").color;
|
||||
document.documentElement.removeAttribute("class");
|
||||
}
|
||||
|
||||
</script>
|
||||
<div style="width: 50em">
|
||||
<span id="test" style="color: green">
|
||||
This is some text with a transition.
|
||||
</span>
|
||||
</div>
|
@ -0,0 +1,31 @@
|
||||
<html class="reftest-wait">
|
||||
<title>Test for CSS transitions and re-wrapping of inlines</title>
|
||||
<style type="text/css">
|
||||
#test { -moz-transition: 20ms color linear 0; }
|
||||
</style>
|
||||
<script type="text/javascript">
|
||||
|
||||
window.onload = run;
|
||||
|
||||
function run() {
|
||||
var test = document.getElementById("test");
|
||||
var unused = test.offsetWidth;
|
||||
unused = getComputedStyle(test, "").color;
|
||||
test.style.color = "green";
|
||||
unused = test.offsetWidth;
|
||||
unused = getComputedStyle(test, "").color;
|
||||
test.parentNode.style.width = "3em";
|
||||
unused = test.offsetWidth;
|
||||
unused = getComputedStyle(test, "").color;
|
||||
setTimeout(step2, 100); // give transition time to run
|
||||
}
|
||||
function step2() {
|
||||
document.documentElement.removeAttribute("class");
|
||||
}
|
||||
|
||||
</script>
|
||||
<div style="width: 50em">
|
||||
<span id="test" style="color: red">
|
||||
This is some text with a transition.
|
||||
</span>
|
||||
</div>
|
@ -53,6 +53,9 @@ include css-mediaqueries/reftest.list
|
||||
# css namespaces
|
||||
include css-namespace/reftest.list
|
||||
|
||||
# css transitions
|
||||
include css-transitions/reftest.list
|
||||
|
||||
# css values and units
|
||||
include css-valuesandunits/reftest.list
|
||||
|
||||
|
@ -141,6 +141,7 @@ CPPSRCS = \
|
||||
nsStyleStruct.cpp \
|
||||
nsStyleTransformMatrix.cpp \
|
||||
nsStyleUtil.cpp \
|
||||
nsTransitionManager.cpp \
|
||||
$(NULL)
|
||||
|
||||
FORCE_STATIC_LIB = 1
|
||||
|
@ -58,6 +58,7 @@
|
||||
#include "nsIFrame.h"
|
||||
#include "nsContentUtils.h"
|
||||
#include "nsRuleProcessorData.h"
|
||||
#include "nsTransitionManager.h"
|
||||
|
||||
NS_IMPL_ISUPPORTS1(nsEmptyStyleRule, nsIStyleRule)
|
||||
|
||||
@ -113,6 +114,8 @@ nsStyleSet::Init(nsPresContext *aPresContext)
|
||||
return NS_ERROR_OUT_OF_MEMORY;
|
||||
}
|
||||
|
||||
GatherRuleProcessors(eTransitionSheet);
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
@ -192,6 +195,13 @@ nsStyleSet::GatherRuleProcessors(sheetType aType)
|
||||
//don't regather if this level is disabled
|
||||
return NS_OK;
|
||||
}
|
||||
if (aType == eTransitionSheet) {
|
||||
// We have no sheet for the transitions level; just a rule
|
||||
// processor. (XXX: We should probably do this for the other
|
||||
// non-CSS levels too!)
|
||||
mRuleProcessors[aType] = PresContext()->TransitionManager();
|
||||
return NS_OK;
|
||||
}
|
||||
if (mSheets[aType].Count()) {
|
||||
switch (aType) {
|
||||
case eAgentSheet:
|
||||
@ -586,6 +596,16 @@ nsStyleSet::FileRules(nsIStyleRuleProcessor::EnumFunc aCollectorFunc,
|
||||
aRuleWalker->SetLevel(eAgentSheet, PR_TRUE);
|
||||
AddImportantRules(lastAgentRN, nsnull, aRuleWalker); //agent
|
||||
|
||||
#ifdef DEBUG
|
||||
nsRuleNode *lastImportantRN = aRuleWalker->GetCurrentNode();
|
||||
#endif
|
||||
aRuleWalker->SetLevel(eTransitionSheet, PR_FALSE);
|
||||
(*aCollectorFunc)(mRuleProcessors[eTransitionSheet], aData);
|
||||
#ifdef DEBUG
|
||||
AssertNoCSSRules(aRuleWalker->GetCurrentNode(), lastImportantRN);
|
||||
AssertNoImportantRules(aRuleWalker->GetCurrentNode(), lastImportantRN);
|
||||
#endif
|
||||
|
||||
}
|
||||
|
||||
// Enumerate all the rules in a way that doesn't care about the order
|
||||
@ -623,6 +643,7 @@ nsStyleSet::WalkRuleProcessors(nsIStyleRuleProcessor::EnumFunc aFunc,
|
||||
(*aFunc)(mRuleProcessors[eStyleAttrSheet], aData);
|
||||
if (mRuleProcessors[eOverrideSheet])
|
||||
(*aFunc)(mRuleProcessors[eOverrideSheet], aData);
|
||||
(*aFunc)(mRuleProcessors[eTransitionSheet], aData);
|
||||
}
|
||||
|
||||
PRBool nsStyleSet::BuildDefaultStyleData(nsPresContext* aPresContext)
|
||||
|
@ -196,6 +196,7 @@ class nsStyleSet
|
||||
eDocSheet, // CSS
|
||||
eStyleAttrSheet,
|
||||
eOverrideSheet, // CSS
|
||||
eTransitionSheet,
|
||||
eSheetTypeCount
|
||||
// be sure to keep the number of bits in |mDirty| below and in
|
||||
// NS_RULE_NODE_LEVEL_MASK updated when changing the number of sheet
|
||||
@ -351,7 +352,7 @@ class nsStyleSet
|
||||
unsigned mInShutdown : 1;
|
||||
unsigned mAuthorStyleDisabled: 1;
|
||||
unsigned mInReconstruct : 1;
|
||||
unsigned mDirty : 7; // one dirty bit is used per sheet type
|
||||
unsigned mDirty : 8; // one dirty bit is used per sheet type
|
||||
|
||||
};
|
||||
|
||||
|
@ -1909,6 +1909,14 @@ nsChangeHint nsStyleDisplay::CalcDifference(const nsStyleDisplay& aOther) const
|
||||
}
|
||||
}
|
||||
|
||||
// Note: Our current behavior for handling changes to transition
|
||||
// properties is to do nothing. In other words, the transition
|
||||
// property that matters is what it is when the transition begins, and
|
||||
// we don't stop a transition later because the transition property
|
||||
// changed.
|
||||
// FIXME: Need to test for this and write it in the spec, if it's
|
||||
// compatible with other browsers. Test for behavior at
|
||||
// http://dbaron.org/css/test/2009/transitions/dynamic-transition-change
|
||||
|
||||
return hint;
|
||||
}
|
||||
|
899
layout/style/nsTransitionManager.cpp
Normal file
899
layout/style/nsTransitionManager.cpp
Normal file
@ -0,0 +1,899 @@
|
||||
/* vim: set shiftwidth=2 tabstop=8 autoindent cindent expandtab: */
|
||||
/* ***** BEGIN LICENSE BLOCK *****
|
||||
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
|
||||
*
|
||||
* The contents of this file are subject to the Mozilla Public License Version
|
||||
* 1.1 (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
* http://www.mozilla.org/MPL/
|
||||
*
|
||||
* Software distributed under the License is distributed on an "AS IS" basis,
|
||||
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
|
||||
* for the specific language governing rights and limitations under the
|
||||
* License.
|
||||
*
|
||||
* The Original Code is nsTransitionManager.
|
||||
*
|
||||
* The Initial Developer of the Original Code is the Mozilla Foundation.
|
||||
* Portions created by the Initial Developer are Copyright (C) 2009
|
||||
* the Initial Developer. All Rights Reserved.
|
||||
*
|
||||
* Contributor(s):
|
||||
* L. David Baron <dbaron@dbaron.org>, Mozilla Corporation (original author)
|
||||
*
|
||||
* Alternatively, the contents of this file may be used under the terms of
|
||||
* either the GNU General Public License Version 2 or later (the "GPL"), or
|
||||
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
|
||||
* in which case the provisions of the GPL or the LGPL are applicable instead
|
||||
* of those above. If you wish to allow use of your version of this file only
|
||||
* under the terms of either the GPL or the LGPL, and not to allow others to
|
||||
* use your version of this file under the terms of the MPL, indicate your
|
||||
* decision by deleting the provisions above and replace them with the notice
|
||||
* and other provisions required by the GPL or the LGPL. If you do not delete
|
||||
* the provisions above, a recipient may use your version of this file under
|
||||
* the terms of any one of the MPL, the GPL or the LGPL.
|
||||
*
|
||||
* ***** END LICENSE BLOCK ***** */
|
||||
|
||||
/* Code to start and animate CSS transitions. */
|
||||
|
||||
#include "nsTransitionManager.h"
|
||||
#include "nsIContent.h"
|
||||
#include "nsStyleContext.h"
|
||||
#include "nsCSSProps.h"
|
||||
#include "mozilla/TimeStamp.h"
|
||||
#include "nsRefreshDriver.h"
|
||||
#include "nsRuleProcessorData.h"
|
||||
#include "nsIStyleRule.h"
|
||||
#include "nsRuleWalker.h"
|
||||
#include "nsRuleData.h"
|
||||
#include "nsSMILKeySpline.h"
|
||||
#include "gfxColor.h"
|
||||
#include "nsCSSPseudoElements.h"
|
||||
#include "nsCSSPropertySet.h"
|
||||
#include "nsStyleAnimation.h"
|
||||
|
||||
using mozilla::TimeStamp;
|
||||
using mozilla::TimeDuration;
|
||||
|
||||
/*****************************************************************************
|
||||
* Per-Element data *
|
||||
*****************************************************************************/
|
||||
|
||||
struct ElementPropertyTransition
|
||||
{
|
||||
nsCSSProperty mProperty;
|
||||
nsStyleCoord mStartValue, mEndValue;
|
||||
TimeStamp mStartTime; // actual start plus transition delay
|
||||
|
||||
// data from the relevant nsTransition
|
||||
TimeDuration mDuration;
|
||||
nsSMILKeySpline mTimingFunction;
|
||||
};
|
||||
|
||||
/**
|
||||
* An ElementTransitionsStyleRule overrides style data with the
|
||||
* currently-transitioning value for an element that is executing a
|
||||
* transition. It only matches when styling with animation. When we
|
||||
* style without animation, we need to not use it so that we can detect
|
||||
* any new changes; if necessary we restyle immediately afterwards with
|
||||
* animation.
|
||||
*/
|
||||
class ElementTransitionsStyleRule : public nsIStyleRule
|
||||
{
|
||||
public:
|
||||
// nsISupportsImplementation
|
||||
NS_DECL_ISUPPORTS
|
||||
|
||||
// nsIStyleRule implementation
|
||||
NS_IMETHOD MapRuleInfoInto(nsRuleData* aRuleData);
|
||||
#ifdef DEBUG
|
||||
NS_IMETHOD List(FILE* out = stdout, PRInt32 aIndent = 0) const;
|
||||
#endif
|
||||
|
||||
ElementTransitionsStyleRule(ElementTransitions *aOwner,
|
||||
TimeStamp aRefreshTime)
|
||||
: mElementTransitions(aOwner)
|
||||
, mRefreshTime(aRefreshTime)
|
||||
{}
|
||||
|
||||
void Disconnect() { mElementTransitions = nsnull; }
|
||||
|
||||
ElementTransitions *ElementData() { return mElementTransitions; }
|
||||
TimeStamp RefreshTime() { return mRefreshTime; }
|
||||
|
||||
private:
|
||||
ElementTransitions *mElementTransitions;
|
||||
// The time stamp for which this style rule is valid.
|
||||
TimeStamp mRefreshTime;
|
||||
};
|
||||
|
||||
/**
|
||||
* A CoverTransitionStyleRule sets any value for which we're starting a
|
||||
* transition back to the pre-transition value for the period when we're
|
||||
* resolving style on its descendants, so that we have the required
|
||||
* behavior for initiating transitions on such descendants. For more
|
||||
* detail, see comment below, above "new CoverTransitionStartStyleRule".
|
||||
*/
|
||||
class CoverTransitionStartStyleRule : public nsIStyleRule
|
||||
{
|
||||
public:
|
||||
// nsISupportsImplementation
|
||||
NS_DECL_ISUPPORTS
|
||||
|
||||
// nsIStyleRule implementation
|
||||
NS_IMETHOD MapRuleInfoInto(nsRuleData* aRuleData);
|
||||
#ifdef DEBUG
|
||||
NS_IMETHOD List(FILE* out = stdout, PRInt32 aIndent = 0) const;
|
||||
#endif
|
||||
|
||||
NS_HIDDEN_(void) CoverValue(nsCSSProperty aProperty,
|
||||
nsStyleCoord &aStartValue)
|
||||
{
|
||||
CoveredValue v = { aProperty, aStartValue };
|
||||
mCoveredValues.AppendElement(v);
|
||||
}
|
||||
|
||||
NS_HIDDEN_(void)
|
||||
FillStyleStruct(void* aStyleStruct, nsRuleData* aRuleData) const;
|
||||
|
||||
struct CoveredValue {
|
||||
nsCSSProperty mProperty;
|
||||
nsStyleCoord mCoveredValue;
|
||||
};
|
||||
|
||||
private:
|
||||
nsTArray<CoveredValue> mCoveredValues;
|
||||
};
|
||||
|
||||
struct ElementTransitions : public PRCList
|
||||
{
|
||||
ElementTransitions(nsIContent *aElement, nsIAtom *aElementProperty,
|
||||
nsTransitionManager *aTransitionManager)
|
||||
: mElement(aElement)
|
||||
, mElementProperty(aElementProperty)
|
||||
, mTransitionManager(aTransitionManager)
|
||||
{
|
||||
PR_INIT_CLIST(this);
|
||||
}
|
||||
~ElementTransitions()
|
||||
{
|
||||
DropStyleRule();
|
||||
PR_REMOVE_LINK(this);
|
||||
mTransitionManager->TransitionsRemoved();
|
||||
}
|
||||
|
||||
void Destroy()
|
||||
{
|
||||
// This will call our destructor.
|
||||
mElement->DeleteProperty(mElementProperty);
|
||||
}
|
||||
|
||||
void DropStyleRule();
|
||||
PRBool EnsureStyleRuleFor(TimeStamp aRefreshTime);
|
||||
|
||||
|
||||
// Either zero or one for each CSS property:
|
||||
nsTArray<ElementPropertyTransition> mPropertyTransitions;
|
||||
|
||||
// The style rule for the transitions (which contains the time stamp
|
||||
// for which it is valid).
|
||||
nsRefPtr<ElementTransitionsStyleRule> mStyleRule;
|
||||
|
||||
nsIContent *mElement;
|
||||
|
||||
// the atom we use in mElement's prop table (must be a static atom,
|
||||
// i.e., in an atom list)
|
||||
nsIAtom *mElementProperty;
|
||||
|
||||
nsTransitionManager *mTransitionManager;
|
||||
};
|
||||
|
||||
static void
|
||||
ElementTransitionsPropertyDtor(void *aObject,
|
||||
nsIAtom *aPropertyName,
|
||||
void *aPropertyValue,
|
||||
void *aData)
|
||||
{
|
||||
ElementTransitions *et = static_cast<ElementTransitions*>(aPropertyValue);
|
||||
delete et;
|
||||
}
|
||||
|
||||
NS_IMPL_ISUPPORTS1(ElementTransitionsStyleRule, nsIStyleRule)
|
||||
|
||||
static void
|
||||
ElementTransitionsPostResolveCallback(void* aStyleStruct, nsRuleData* aRuleData,
|
||||
nsIStyleRule* aRule)
|
||||
{
|
||||
ElementTransitionsStyleRule *rule =
|
||||
static_cast<ElementTransitionsStyleRule*>(aRule);
|
||||
ElementTransitions *et = rule->ElementData();
|
||||
for (PRUint32 i = 0, i_end = et->mPropertyTransitions.Length();
|
||||
i < i_end; ++i)
|
||||
{
|
||||
const ElementPropertyTransition &pt = et->mPropertyTransitions[i];
|
||||
if (aRuleData->mSIDs & nsCachedStyleData::GetBitForSID(
|
||||
nsCSSProps::kSIDTable[pt.mProperty]))
|
||||
{
|
||||
double timePortion =
|
||||
(rule->RefreshTime() - pt.mStartTime).ToSeconds() /
|
||||
pt.mDuration.ToSeconds();
|
||||
if (timePortion < 0.0)
|
||||
timePortion = 0.0; // use start value during transition-delay
|
||||
if (timePortion > 1.0)
|
||||
timePortion = 1.0; // we might be behind on flushing
|
||||
|
||||
double valuePortion =
|
||||
pt.mTimingFunction.GetSplineValue(timePortion);
|
||||
nsStyleCoord value;
|
||||
#ifdef DEBUG
|
||||
PRBool ok =
|
||||
#endif
|
||||
nsStyleAnimation::Interpolate(pt.mStartValue, pt.mEndValue,
|
||||
valuePortion, value);
|
||||
NS_ABORT_IF_FALSE(ok, "could not interpolate values");
|
||||
#ifdef DEBUG
|
||||
ok =
|
||||
#endif
|
||||
nsStyleAnimation::StoreComputedValue(pt.mProperty,
|
||||
aRuleData->mPresContext,
|
||||
aStyleStruct, value);
|
||||
NS_ABORT_IF_FALSE(ok, "could not store computed value");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
ElementTransitionsStyleRule::MapRuleInfoInto(nsRuleData* aRuleData)
|
||||
{
|
||||
nsStyleContext *contextParent = aRuleData->mStyleContext->GetParent();
|
||||
if (contextParent && contextParent->HasPseudoElementData()) {
|
||||
// Don't apply transitions to things inside of pseudo-elements.
|
||||
// FIXME: Add tests for this.
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
ElementTransitions *et = ElementData();
|
||||
NS_ENSURE_TRUE(et, NS_OK); // FIXME: Why can this be null?
|
||||
for (PRUint32 i = 0, i_end = et->mPropertyTransitions.Length();
|
||||
i < i_end; ++i)
|
||||
{
|
||||
ElementPropertyTransition &pt = et->mPropertyTransitions[i];
|
||||
if (aRuleData->mSIDs & nsCachedStyleData::GetBitForSID(
|
||||
nsCSSProps::kSIDTable[pt.mProperty]))
|
||||
{
|
||||
nsPostResolveCallback prc =
|
||||
{ &ElementTransitionsPostResolveCallback, this };
|
||||
aRuleData->mPostResolveCallbacks.AppendElement(prc);
|
||||
// This really doesn't matter much, since this ought to be
|
||||
// the only node with the rule, but it's good practice for
|
||||
// post-resolve callbacks.
|
||||
aRuleData->mCanStoreInRuleTree = PR_FALSE;
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
}
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
#ifdef DEBUG
|
||||
NS_IMETHODIMP
|
||||
ElementTransitionsStyleRule::List(FILE* out, PRInt32 aIndent) const
|
||||
{
|
||||
// WRITE ME?
|
||||
return NS_OK;
|
||||
}
|
||||
#endif
|
||||
|
||||
void
|
||||
ElementTransitions::DropStyleRule()
|
||||
{
|
||||
if (mStyleRule) {
|
||||
mStyleRule->Disconnect();
|
||||
mStyleRule = nsnull;
|
||||
}
|
||||
}
|
||||
|
||||
PRBool
|
||||
ElementTransitions::EnsureStyleRuleFor(TimeStamp aRefreshTime)
|
||||
{
|
||||
if (!mStyleRule || mStyleRule->RefreshTime() != aRefreshTime) {
|
||||
DropStyleRule();
|
||||
|
||||
ElementTransitionsStyleRule *newRule =
|
||||
new ElementTransitionsStyleRule(this, aRefreshTime);
|
||||
if (!newRule) {
|
||||
NS_WARNING("out of memory");
|
||||
return PR_FALSE;
|
||||
}
|
||||
|
||||
mStyleRule = newRule;
|
||||
}
|
||||
|
||||
return PR_TRUE;
|
||||
}
|
||||
|
||||
NS_IMPL_ISUPPORTS1(CoverTransitionStartStyleRule, nsIStyleRule)
|
||||
|
||||
static void
|
||||
CoverTransitionStartPostResolveCallback(void* aStyleStruct,
|
||||
nsRuleData* aRuleData,
|
||||
nsIStyleRule* aRule)
|
||||
{
|
||||
CoverTransitionStartStyleRule* coverRule =
|
||||
static_cast<CoverTransitionStartStyleRule*>(aRule);
|
||||
coverRule->FillStyleStruct(aStyleStruct, aRuleData);
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
CoverTransitionStartStyleRule::MapRuleInfoInto(nsRuleData* aRuleData)
|
||||
{
|
||||
for (PRUint32 i = 0, i_end = mCoveredValues.Length(); i < i_end; ++i) {
|
||||
CoveredValue &cv = mCoveredValues[i];
|
||||
if (aRuleData->mSIDs & nsCachedStyleData::GetBitForSID(
|
||||
nsCSSProps::kSIDTable[cv.mProperty]))
|
||||
{
|
||||
nsPostResolveCallback prc =
|
||||
{ &CoverTransitionStartPostResolveCallback, this };
|
||||
aRuleData->mPostResolveCallbacks.AppendElement(prc);
|
||||
// This really doesn't matter much, since this ought to be
|
||||
// the only node with the rule, but it's good practice for
|
||||
// post-resolve callbacks.
|
||||
aRuleData->mCanStoreInRuleTree = PR_FALSE;
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
}
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
#ifdef DEBUG
|
||||
NS_IMETHODIMP
|
||||
CoverTransitionStartStyleRule::List(FILE* out, PRInt32 aIndent) const
|
||||
{
|
||||
// WRITE ME?
|
||||
return NS_OK;
|
||||
}
|
||||
#endif
|
||||
|
||||
void
|
||||
CoverTransitionStartStyleRule::FillStyleStruct(void* aStyleStruct,
|
||||
nsRuleData* aRuleData) const
|
||||
{
|
||||
for (PRUint32 i = 0, i_end = mCoveredValues.Length(); i < i_end; ++i) {
|
||||
const CoveredValue &cv = mCoveredValues[i];
|
||||
if (aRuleData->mSIDs & nsCachedStyleData::GetBitForSID(
|
||||
nsCSSProps::kSIDTable[cv.mProperty]))
|
||||
{
|
||||
#ifdef DEBUG
|
||||
PRBool ok =
|
||||
#endif
|
||||
nsStyleAnimation::StoreComputedValue(cv.mProperty,
|
||||
aRuleData->mPresContext,
|
||||
aStyleStruct, cv.mCoveredValue);
|
||||
NS_ABORT_IF_FALSE(ok, "could not store computed value");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*****************************************************************************
|
||||
* nsTransitionManager *
|
||||
*****************************************************************************/
|
||||
|
||||
nsTransitionManager::nsTransitionManager(nsPresContext *aPresContext)
|
||||
: mPresContext(aPresContext)
|
||||
{
|
||||
PR_INIT_CLIST(&mElementTransitions);
|
||||
}
|
||||
|
||||
nsTransitionManager::~nsTransitionManager()
|
||||
{
|
||||
// Content nodes might outlive the transition manager.
|
||||
while (!PR_CLIST_IS_EMPTY(&mElementTransitions)) {
|
||||
ElementTransitions *head = static_cast<ElementTransitions*>(
|
||||
PR_LIST_HEAD(&mElementTransitions));
|
||||
head->Destroy();
|
||||
}
|
||||
}
|
||||
|
||||
already_AddRefed<nsIStyleRule>
|
||||
nsTransitionManager::StyleContextChanged(nsIContent *aElement,
|
||||
nsStyleContext *aOldStyleContext,
|
||||
nsStyleContext *aNewStyleContext)
|
||||
{
|
||||
NS_PRECONDITION(aOldStyleContext->GetPseudoType() ==
|
||||
aNewStyleContext->GetPseudoType(),
|
||||
"pseudo type mismatch");
|
||||
// If we were called from ReParentStyleContext, this assertion would
|
||||
// actually fire. If we need to be called from there, we can probably
|
||||
// just remove it; the condition probably isn't critical, although
|
||||
// it's worth thinking about some more.
|
||||
NS_PRECONDITION(aOldStyleContext->HasPseudoElementData() ==
|
||||
aNewStyleContext->HasPseudoElementData(),
|
||||
"pseudo type mismatch");
|
||||
|
||||
// Return sooner (before the startedAny check below) for the most
|
||||
// common case: no transitions specified.
|
||||
const nsStyleDisplay *disp = aNewStyleContext->GetStyleDisplay();
|
||||
if (disp->mTransitionPropertyCount == 1 &&
|
||||
disp->mTransitions[0].GetDelay() == 0.0f &&
|
||||
disp->mTransitions[0].GetDuration() == 0.0f) {
|
||||
return nsnull;
|
||||
}
|
||||
|
||||
|
||||
if (aNewStyleContext->PresContext()->IsProcessingAnimationStyleChange()) {
|
||||
return nsnull;
|
||||
}
|
||||
|
||||
nsIAtom *pseudo = aNewStyleContext->GetPseudoType();
|
||||
if (pseudo && (pseudo != nsCSSPseudoElements::before &&
|
||||
pseudo != nsCSSPseudoElements::after)) {
|
||||
return nsnull;
|
||||
}
|
||||
if (aNewStyleContext->GetParent() &&
|
||||
aNewStyleContext->GetParent()->HasPseudoElementData()) {
|
||||
// Ignore transitions on things that inherit properties from
|
||||
// pseudo-elements.
|
||||
// FIXME: Add tests for this.
|
||||
return nsnull;
|
||||
}
|
||||
|
||||
// FIXME: When we have multiple continuations, we actually repeat this
|
||||
// for each one, and if we have transitions we create separate cover
|
||||
// rules for each one. However, since we're attaching the transition
|
||||
// data to the element, during the animation we create the same style
|
||||
// rule, so it's not too horrible.
|
||||
|
||||
// Per http://lists.w3.org/Archives/Public/www-style/2009Aug/0109.html
|
||||
// I'll consider only the transitions from the number of items in
|
||||
// 'transition-property' on down, and later ones will override earlier
|
||||
// ones (tracked using |whichStarted|).
|
||||
PRBool startedAny = PR_FALSE;
|
||||
nsCSSPropertySet whichStarted;
|
||||
ElementTransitions *et = nsnull;
|
||||
for (PRUint32 i = disp->mTransitionPropertyCount; i-- != 0; ) {
|
||||
const nsTransition& t = disp->mTransitions[i];
|
||||
// Check delay and duration first, since they default to zero, and
|
||||
// when they're both zero, we can ignore the transition.
|
||||
if (t.GetDelay() != 0.0f || t.GetDuration() != 0.0f) {
|
||||
et = GetElementTransitions(aElement,
|
||||
aNewStyleContext->GetPseudoType(),
|
||||
PR_FALSE);
|
||||
|
||||
// We might have something to transition. See if any of the
|
||||
// properties in question changed and are animatable.
|
||||
nsCSSProperty property = t.GetProperty();
|
||||
if (property == eCSSPropertyExtra_no_properties ||
|
||||
property == eCSSProperty_UNKNOWN) {
|
||||
// Nothing to do, but need to exclude this from cases below.
|
||||
} else if (property == eCSSPropertyExtra_all_properties) {
|
||||
for (nsCSSProperty p = nsCSSProperty(0);
|
||||
p < eCSSProperty_COUNT_no_shorthands;
|
||||
p = nsCSSProperty(p + 1)) {
|
||||
ConsiderStartingTransition(p, t, aElement, et,
|
||||
aOldStyleContext, aNewStyleContext,
|
||||
&startedAny, &whichStarted);
|
||||
}
|
||||
} else if (nsCSSProps::IsShorthand(property)) {
|
||||
CSSPROPS_FOR_SHORTHAND_SUBPROPERTIES(subprop, property) {
|
||||
ConsiderStartingTransition(*subprop, t, aElement, et,
|
||||
aOldStyleContext, aNewStyleContext,
|
||||
&startedAny, &whichStarted);
|
||||
}
|
||||
} else {
|
||||
ConsiderStartingTransition(property, t, aElement, et,
|
||||
aOldStyleContext, aNewStyleContext,
|
||||
&startedAny, &whichStarted);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!startedAny) {
|
||||
return nsnull;
|
||||
}
|
||||
|
||||
NS_ABORT_IF_FALSE(et, "must have element transitions if we started "
|
||||
"any transitions");
|
||||
|
||||
// In the CSS working group discussion (2009 Jul 15 telecon,
|
||||
// http://www.w3.org/mid/4A5E1470.4030904@inkedblade.net ) of
|
||||
// http://lists.w3.org/Archives/Public/www-style/2009Jun/0121.html ,
|
||||
// the working group decided that a transition property on an
|
||||
// element should not cause any transitions if the property change
|
||||
// is itself inheriting a value that is transitioning on an
|
||||
// ancestor. So, to get the correct behavior, we continue the
|
||||
// restyle that caused this transition using a "covering" rule that
|
||||
// covers up any changes on which we started transitions, so that
|
||||
// descendants don't start their own transitions. (In the case of
|
||||
// negative transition delay, this covering rule produces different
|
||||
// results than applying the transition rule immediately would).
|
||||
// Our caller is responsible for restyling again using this covering
|
||||
// rule.
|
||||
|
||||
nsRefPtr<CoverTransitionStartStyleRule> coverRule =
|
||||
new CoverTransitionStartStyleRule;
|
||||
if (!coverRule) {
|
||||
NS_WARNING("out of memory");
|
||||
return nsnull;
|
||||
}
|
||||
|
||||
nsTArray<ElementPropertyTransition> &pts = et->mPropertyTransitions;
|
||||
for (PRUint32 i = 0, i_end = pts.Length(); i < i_end; ++i) {
|
||||
ElementPropertyTransition &pt = pts[i];
|
||||
if (whichStarted.HasProperty(pt.mProperty)) {
|
||||
coverRule->CoverValue(pt.mProperty, pt.mStartValue);
|
||||
}
|
||||
}
|
||||
|
||||
return already_AddRefed<nsIStyleRule>(
|
||||
static_cast<nsIStyleRule*>(coverRule.forget().get()));
|
||||
}
|
||||
|
||||
void
|
||||
nsTransitionManager::ConsiderStartingTransition(nsCSSProperty aProperty,
|
||||
const nsTransition& aTransition,
|
||||
nsIContent *aElement,
|
||||
ElementTransitions *&aElementTransitions,
|
||||
nsStyleContext *aOldStyleContext,
|
||||
nsStyleContext *aNewStyleContext,
|
||||
PRBool *aStartedAny,
|
||||
nsCSSPropertySet *aWhichStarted)
|
||||
{
|
||||
// IsShorthand itself will assert if aProperty is not a property.
|
||||
NS_ABORT_IF_FALSE(!nsCSSProps::IsShorthand(aProperty),
|
||||
"property out of range");
|
||||
|
||||
if (aWhichStarted->HasProperty(aProperty)) {
|
||||
// A later item in transition-property already started a
|
||||
// transition for this property, so we ignore this one.
|
||||
// See comment above and
|
||||
// http://lists.w3.org/Archives/Public/www-style/2009Aug/0109.html .
|
||||
return;
|
||||
}
|
||||
|
||||
if (nsCSSProps::kAnimTypeTable[aProperty] == eStyleAnimType_None) {
|
||||
return;
|
||||
}
|
||||
|
||||
ElementPropertyTransition pt;
|
||||
nsStyleCoord dummyValue;
|
||||
// FIXME: This call on the old style context gets incorrect style data
|
||||
// since we don't quite enforce style rule immutability: we didn't
|
||||
// need to worry about callers calling GetStyleData rather than
|
||||
// PeekStyleData after a style rule becomes "old" before transitions
|
||||
// existed.
|
||||
PRBool shouldAnimate =
|
||||
nsStyleAnimation::ExtractComputedValue(aProperty, aOldStyleContext,
|
||||
pt.mStartValue) &&
|
||||
nsStyleAnimation::ExtractComputedValue(aProperty, aNewStyleContext,
|
||||
pt.mEndValue) &&
|
||||
pt.mStartValue != pt.mEndValue &&
|
||||
// Check that we can interpolate between these values
|
||||
// (If this is ever a performance problem, we could add a
|
||||
// CanInterpolate method, but it seems fine for now.)
|
||||
nsStyleAnimation::Interpolate(pt.mStartValue, pt.mEndValue, 0.5,
|
||||
dummyValue);
|
||||
|
||||
PRUint32 currentIndex = nsTArray<ElementPropertyTransition>::NoIndex;
|
||||
if (aElementTransitions) {
|
||||
nsTArray<ElementPropertyTransition> &pts =
|
||||
aElementTransitions->mPropertyTransitions;
|
||||
for (PRUint32 i = 0, i_end = pts.Length(); i < i_end; ++i) {
|
||||
if (pts[i].mProperty == aProperty) {
|
||||
currentIndex = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
nsPresContext *presContext = aNewStyleContext->PresContext();
|
||||
|
||||
if (!shouldAnimate) {
|
||||
if (currentIndex != nsTArray<ElementPropertyTransition>::NoIndex) {
|
||||
// We're in the middle of a transition, but just got a
|
||||
// non-transition style change changing to exactly the
|
||||
// current in-progress value. (This is quite easy to cause
|
||||
// using 'transition-delay'.)
|
||||
nsTArray<ElementPropertyTransition> &pts =
|
||||
aElementTransitions->mPropertyTransitions;
|
||||
pts.RemoveElementAt(currentIndex);
|
||||
if (pts.IsEmpty()) {
|
||||
aElementTransitions->Destroy();
|
||||
// |aElementTransitions| is now a dangling pointer!
|
||||
aElementTransitions = nsnull;
|
||||
}
|
||||
presContext->PresShell()->RestyleForAnimation(aElement);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// When we interrupt a running transition, we want to reduce the
|
||||
// duration of the new transition *if* the new transition would have
|
||||
// been longer had it started from the endpoint of the currently
|
||||
// running transition.
|
||||
double durationFraction = 1.0;
|
||||
|
||||
// We need to check two things if we have a currently running
|
||||
// transition for this property: see durationFraction comment above
|
||||
// and the endpoint check below.
|
||||
if (currentIndex != nsTArray<ElementPropertyTransition>::NoIndex) {
|
||||
const nsStyleCoord &endVal =
|
||||
aElementTransitions->mPropertyTransitions[currentIndex].mEndValue;
|
||||
|
||||
if (endVal == pt.mEndValue) {
|
||||
// If we got a style change that changed the value to the endpoint
|
||||
// of the currently running transition, we don't want to interrupt
|
||||
// its timing function.
|
||||
// But don't forget to restyle with animation so we show the
|
||||
// current transition.
|
||||
presContext->PresShell()->RestyleForAnimation(aElement);
|
||||
return;
|
||||
}
|
||||
|
||||
double fullDistance, remainingDistance;
|
||||
#ifdef DEBUG
|
||||
PRBool ok =
|
||||
#endif
|
||||
nsStyleAnimation::ComputeDistance(pt.mStartValue, pt.mEndValue,
|
||||
fullDistance);
|
||||
NS_ABORT_IF_FALSE(ok, "could not compute distance");
|
||||
NS_ABORT_IF_FALSE(fullDistance >= 0.0, "distance must be positive");
|
||||
|
||||
if (nsStyleAnimation::ComputeDistance(endVal, pt.mEndValue,
|
||||
remainingDistance)) {
|
||||
NS_ABORT_IF_FALSE(remainingDistance >= 0.0, "distance must be positive");
|
||||
durationFraction = fullDistance / remainingDistance;
|
||||
if (durationFraction > 1.0) {
|
||||
durationFraction = 1.0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
nsRefreshDriver *rd = presContext->RefreshDriver();
|
||||
|
||||
pt.mProperty = aProperty;
|
||||
float delay = aTransition.GetDelay();
|
||||
float duration = aTransition.GetDuration();
|
||||
if (durationFraction != 1.0) {
|
||||
// Negative delays are essentially part of the transition
|
||||
// function, so reduce them along with the duration, but don't
|
||||
// reduce positive delays. (See comment above about
|
||||
// durationFraction.)
|
||||
if (delay < 0.0f)
|
||||
delay *= durationFraction;
|
||||
duration *= durationFraction;
|
||||
}
|
||||
pt.mStartTime = rd->MostRecentRefresh() +
|
||||
TimeDuration::FromMilliseconds(delay);
|
||||
pt.mDuration = TimeDuration::FromMilliseconds(duration);
|
||||
const nsTimingFunction &tf = aTransition.GetTimingFunction();
|
||||
pt.mTimingFunction.Init(tf.mX1, tf.mY1, tf.mX2, tf.mY2);
|
||||
|
||||
if (!aElementTransitions) {
|
||||
aElementTransitions =
|
||||
GetElementTransitions(aElement, aNewStyleContext->GetPseudoType(),
|
||||
PR_TRUE);
|
||||
if (!aElementTransitions) {
|
||||
NS_WARNING("allocating ElementTransitions failed");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
nsTArray<ElementPropertyTransition> &pts =
|
||||
aElementTransitions->mPropertyTransitions;
|
||||
#ifdef DEBUG
|
||||
for (PRUint32 i = 0, i_end = pts.Length(); i < i_end; ++i) {
|
||||
NS_ABORT_IF_FALSE(i == currentIndex ||
|
||||
pts[i].mProperty != aProperty,
|
||||
"duplicate transitions for property");
|
||||
}
|
||||
#endif
|
||||
if (currentIndex != nsTArray<ElementPropertyTransition>::NoIndex) {
|
||||
pts[currentIndex] = pt;
|
||||
} else {
|
||||
if (!pts.AppendElement(pt)) {
|
||||
NS_WARNING("out of memory");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
presContext->PresShell()->RestyleForAnimation(aElement);
|
||||
|
||||
*aStartedAny = PR_TRUE;
|
||||
aWhichStarted->AddProperty(aProperty);
|
||||
}
|
||||
|
||||
ElementTransitions*
|
||||
nsTransitionManager::GetElementTransitions(nsIContent *aElement,
|
||||
nsIAtom *aPseudo,
|
||||
PRBool aCreateIfNeeded)
|
||||
{
|
||||
nsIAtom *propName;
|
||||
if (aPseudo == nsCSSPseudoElements::before) {
|
||||
propName = nsGkAtoms::transitionsOfBeforeProperty;
|
||||
} else if (aPseudo == nsCSSPseudoElements::after) {
|
||||
propName = nsGkAtoms::transitionsOfAfterProperty;
|
||||
} else {
|
||||
NS_ASSERTION(!aPseudo || !aCreateIfNeeded,
|
||||
"should never try to create transitions for pseudo "
|
||||
"other than :before or :after");
|
||||
propName = nsGkAtoms::transitionsProperty;
|
||||
}
|
||||
ElementTransitions *et = static_cast<ElementTransitions*>(
|
||||
aElement->GetProperty(propName));
|
||||
if (!et && aCreateIfNeeded) {
|
||||
// FIXME: Consider arena-allocating?
|
||||
et = new ElementTransitions(aElement, propName, this);
|
||||
if (!et) {
|
||||
NS_WARNING("out of memory");
|
||||
return nsnull;
|
||||
}
|
||||
nsresult rv = aElement->SetProperty(propName, et,
|
||||
ElementTransitionsPropertyDtor, nsnull);
|
||||
if (NS_FAILED(rv)) {
|
||||
NS_WARNING("SetProperty failed");
|
||||
delete et;
|
||||
return nsnull;
|
||||
}
|
||||
|
||||
AddElementTransitions(et);
|
||||
}
|
||||
|
||||
return et;
|
||||
}
|
||||
|
||||
void
|
||||
nsTransitionManager::AddElementTransitions(ElementTransitions* aElementTransitions)
|
||||
{
|
||||
if (PR_CLIST_IS_EMPTY(&mElementTransitions)) {
|
||||
// We need to observe the refresh driver.
|
||||
nsRefreshDriver *rd = mPresContext->RefreshDriver();
|
||||
rd->AddRefreshObserver(this, Flush_Style);
|
||||
}
|
||||
|
||||
PR_INSERT_BEFORE(aElementTransitions, &mElementTransitions);
|
||||
}
|
||||
|
||||
/*
|
||||
* nsISupports implementation
|
||||
*/
|
||||
|
||||
NS_IMPL_ADDREF_USING_AGGREGATOR(nsTransitionManager, mPresContext)
|
||||
NS_IMPL_RELEASE_USING_AGGREGATOR(nsTransitionManager, mPresContext)
|
||||
NS_IMPL_QUERY_INTERFACE1(nsTransitionManager, nsIStyleRuleProcessor)
|
||||
|
||||
/*
|
||||
* nsIStyleRuleProcessor implementation
|
||||
*/
|
||||
|
||||
nsresult
|
||||
nsTransitionManager::WalkTransitionRule(RuleProcessorData* aData,
|
||||
nsIAtom *aPseudo)
|
||||
{
|
||||
if (!aData->mPresContext->IsProcessingAnimationStyleChange()) {
|
||||
// If we're processing a normal style change rather than one from
|
||||
// animation, don't add the transition rule. This allows us to
|
||||
// compute the new style value rather than having the transition
|
||||
// override it, so that we can start transitioning differently.
|
||||
|
||||
// In most cases, we need to immediately restyle with animation
|
||||
// after doing this. However, ConsiderStartingTransition takes care
|
||||
// of that for us.
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
ElementTransitions *et =
|
||||
GetElementTransitions(aData->mContent, aPseudo, PR_FALSE);
|
||||
if (!et) {
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
if (!et->EnsureStyleRuleFor(
|
||||
aData->mPresContext->RefreshDriver()->MostRecentRefresh())) {
|
||||
return NS_ERROR_OUT_OF_MEMORY;
|
||||
}
|
||||
|
||||
aData->mRuleWalker->Forward(et->mStyleRule);
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsTransitionManager::RulesMatching(ElementRuleProcessorData* aData)
|
||||
{
|
||||
NS_ABORT_IF_FALSE(aData->mPresContext == mPresContext,
|
||||
"pres context mismatch");
|
||||
return WalkTransitionRule(aData, nsnull);
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsTransitionManager::RulesMatching(PseudoRuleProcessorData* aData)
|
||||
{
|
||||
NS_ABORT_IF_FALSE(aData->mPresContext == mPresContext,
|
||||
"pres context mismatch");
|
||||
// Note: If we're the only thing keeping a pseudo-element frame alive
|
||||
// (per ProbePseudoStyleContext), we still want to keep it alive, so
|
||||
// this is ok.
|
||||
return WalkTransitionRule(aData, aData->mPseudoTag);
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsTransitionManager::HasStateDependentStyle(StateRuleProcessorData* aData,
|
||||
nsReStyleHint* aResult)
|
||||
{
|
||||
*aResult = nsReStyleHint(0);
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsTransitionManager::HasAttributeDependentStyle(AttributeRuleProcessorData* aData,
|
||||
nsReStyleHint* aResult)
|
||||
{
|
||||
*aResult = nsReStyleHint(0);
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsTransitionManager::MediumFeaturesChanged(nsPresContext* aPresContext,
|
||||
PRBool* aRulesChanged)
|
||||
{
|
||||
*aRulesChanged = PR_FALSE;
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
/* virtual */ void
|
||||
nsTransitionManager::WillRefresh(mozilla::TimeStamp aTime)
|
||||
{
|
||||
// Trim transitions that have completed, and post restyle events for
|
||||
// frames that are still transitioning.
|
||||
{
|
||||
PRCList *next = PR_LIST_HEAD(&mElementTransitions);
|
||||
while (next != &mElementTransitions) {
|
||||
ElementTransitions *et = static_cast<ElementTransitions*>(next);
|
||||
next = PR_NEXT_LINK(next);
|
||||
|
||||
NS_ABORT_IF_FALSE(et->mElement->GetCurrentDoc() ==
|
||||
mPresContext->Document(),
|
||||
"nsGenericElement::UnbindFromTree should have "
|
||||
"destroyed the element transitions object");
|
||||
|
||||
PRUint32 i = et->mPropertyTransitions.Length();
|
||||
NS_ABORT_IF_FALSE(i != 0, "empty transitions list?");
|
||||
do {
|
||||
--i;
|
||||
ElementPropertyTransition &pt = et->mPropertyTransitions[i];
|
||||
if (pt.mStartTime + pt.mDuration <= aTime) {
|
||||
// This transition has completed.
|
||||
et->mPropertyTransitions.RemoveElementAt(i);
|
||||
}
|
||||
} while (i != 0);
|
||||
|
||||
// We need to restyle even if the transition rule no longer
|
||||
// applies (in which case we just made it not apply).
|
||||
mPresContext->PresShell()->RestyleForAnimation(et->mElement);
|
||||
|
||||
if (et->mPropertyTransitions.IsEmpty()) {
|
||||
et->Destroy();
|
||||
// |et| is now a dangling pointer!
|
||||
et = nsnull;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// We might have removed transitions above.
|
||||
TransitionsRemoved();
|
||||
}
|
||||
|
||||
void
|
||||
nsTransitionManager::TransitionsRemoved()
|
||||
{
|
||||
// If we have no transitions left, remove ourselves from the refresh
|
||||
// driver.
|
||||
if (PR_CLIST_IS_EMPTY(&mElementTransitions)) {
|
||||
mPresContext->RefreshDriver()->RemoveRefreshObserver(this, Flush_Style);
|
||||
}
|
||||
}
|
121
layout/style/nsTransitionManager.h
Normal file
121
layout/style/nsTransitionManager.h
Normal file
@ -0,0 +1,121 @@
|
||||
/* vim: set shiftwidth=2 tabstop=8 autoindent cindent expandtab: */
|
||||
/* ***** BEGIN LICENSE BLOCK *****
|
||||
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
|
||||
*
|
||||
* The contents of this file are subject to the Mozilla Public License Version
|
||||
* 1.1 (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
* http://www.mozilla.org/MPL/
|
||||
*
|
||||
* Software distributed under the License is distributed on an "AS IS" basis,
|
||||
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
|
||||
* for the specific language governing rights and limitations under the
|
||||
* License.
|
||||
*
|
||||
* The Original Code is nsTransitionManager.
|
||||
*
|
||||
* The Initial Developer of the Original Code is the Mozilla Foundation.
|
||||
* Portions created by the Initial Developer are Copyright (C) 2009
|
||||
* the Initial Developer. All Rights Reserved.
|
||||
*
|
||||
* Contributor(s):
|
||||
* L. David Baron <dbaron@dbaron.org>, Mozilla Corporation (original author)
|
||||
*
|
||||
* Alternatively, the contents of this file may be used under the terms of
|
||||
* either the GNU General Public License Version 2 or later (the "GPL"), or
|
||||
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
|
||||
* in which case the provisions of the GPL or the LGPL are applicable instead
|
||||
* of those above. If you wish to allow use of your version of this file only
|
||||
* under the terms of either the GPL or the LGPL, and not to allow others to
|
||||
* use your version of this file under the terms of the MPL, indicate your
|
||||
* decision by deleting the provisions above and replace them with the notice
|
||||
* and other provisions required by the GPL or the LGPL. If you do not delete
|
||||
* the provisions above, a recipient may use your version of this file under
|
||||
* the terms of any one of the MPL, the GPL or the LGPL.
|
||||
*
|
||||
* ***** END LICENSE BLOCK ***** */
|
||||
|
||||
/* Code to start and animate CSS transitions. */
|
||||
|
||||
#ifndef nsTransitionManager_h_
|
||||
#define nsTransitionManager_h_
|
||||
|
||||
#include "prclist.h"
|
||||
#include "nsCSSProperty.h"
|
||||
#include "nsIStyleRuleProcessor.h"
|
||||
#include "nsRefreshDriver.h"
|
||||
|
||||
class nsStyleContext;
|
||||
class nsPresContext;
|
||||
class nsCSSPropertySet;
|
||||
struct nsTransition;
|
||||
struct ElementTransitions;
|
||||
|
||||
/**
|
||||
* Must be created only as a sub-object of an nsPresContext (since its
|
||||
* reference counting methods assume that).
|
||||
*/
|
||||
class nsTransitionManager : public nsIStyleRuleProcessor,
|
||||
public nsARefreshObserver {
|
||||
public:
|
||||
nsTransitionManager(nsPresContext *aPresContext);
|
||||
~nsTransitionManager();
|
||||
|
||||
/**
|
||||
* StyleContextChanged
|
||||
*
|
||||
* To be called from nsFrameManager::ReResolveStyleContext when the
|
||||
* style of an element has changed, to initiate transitions from that
|
||||
* style change.
|
||||
*
|
||||
* It may return a "cover rule" (see CoverTransitionStartStyleRule) to
|
||||
* cover up some of the changes for the duration of the restyling of
|
||||
* descendants. If it does, this function will take care of causing
|
||||
* the necessary restyle afterwards, but the caller must restyle the
|
||||
* element *again* with the original sequence of rules plus the
|
||||
* returned cover rule as the most specific rule.
|
||||
*/
|
||||
already_AddRefed<nsIStyleRule>
|
||||
StyleContextChanged(nsIContent *aElement,
|
||||
nsStyleContext *aOldStyleContext,
|
||||
nsStyleContext *aNewStyleContext);
|
||||
|
||||
// nsISupports
|
||||
NS_DECL_ISUPPORTS_INHERITED
|
||||
|
||||
// nsIStyleRuleProcessor
|
||||
NS_IMETHOD RulesMatching(ElementRuleProcessorData* aData);
|
||||
NS_IMETHOD RulesMatching(PseudoRuleProcessorData* aData);
|
||||
NS_IMETHOD HasStateDependentStyle(StateRuleProcessorData* aData,
|
||||
nsReStyleHint* aResult);
|
||||
NS_IMETHOD HasAttributeDependentStyle(AttributeRuleProcessorData* aData,
|
||||
nsReStyleHint* aResult);
|
||||
NS_IMETHOD MediumFeaturesChanged(nsPresContext* aPresContext,
|
||||
PRBool* aRulesChanged);
|
||||
|
||||
// nsARefreshObserver
|
||||
virtual void WillRefresh(mozilla::TimeStamp aTime);
|
||||
|
||||
private:
|
||||
friend class ElementTransitions; // for TransitionsRemoved
|
||||
|
||||
void ConsiderStartingTransition(nsCSSProperty aProperty,
|
||||
const nsTransition& aTransition,
|
||||
nsIContent *aElement,
|
||||
ElementTransitions *&aElementTransitions,
|
||||
nsStyleContext *aOldStyleContext,
|
||||
nsStyleContext *aNewStyleContext,
|
||||
PRBool *aStartedAny,
|
||||
nsCSSPropertySet *aWhichStarted);
|
||||
ElementTransitions* GetElementTransitions(nsIContent *aElement,
|
||||
nsIAtom *aPseudo,
|
||||
PRBool aCreateIfNeeded);
|
||||
void AddElementTransitions(ElementTransitions* aElementTransitions);
|
||||
void TransitionsRemoved();
|
||||
nsresult WalkTransitionRule(RuleProcessorData* aData, nsIAtom *aPseudo);
|
||||
|
||||
PRCList mElementTransitions;
|
||||
nsPresContext *mPresContext;
|
||||
};
|
||||
|
||||
#endif /* !defined(nsTransitionManager_h_) */
|
@ -136,6 +136,7 @@ _TEST_FILES = test_acid3_test46.html \
|
||||
test_system_font_serialization.html \
|
||||
test_transitions_computed_values.html \
|
||||
test_transitions_computed_value_combinations.html \
|
||||
test_transitions.html \
|
||||
test_units_angle.html \
|
||||
test_units_frequency.html \
|
||||
test_units_length.html \
|
||||
|
626
layout/style/test/test_transitions.html
Normal file
626
layout/style/test/test_transitions.html
Normal file
@ -0,0 +1,626 @@
|
||||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<!--
|
||||
https://bugzilla.mozilla.org/show_bug.cgi?id=435441
|
||||
-->
|
||||
<head>
|
||||
<title>Test for Bug 435441</title>
|
||||
<script type="application/javascript" src="/MochiKit/packed.js"></script>
|
||||
<script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
|
||||
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
|
||||
<style type="text/css">
|
||||
|
||||
#display p { margin-top: 0; margin-bottom: 0; }
|
||||
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=435441">Mozilla Bug 435441</a>
|
||||
<div id="display">
|
||||
|
||||
</div>
|
||||
<pre id="test">
|
||||
<script type="application/javascript">
|
||||
|
||||
/** Test for Bug 435441 **/
|
||||
|
||||
function px_to_num(str)
|
||||
{
|
||||
return Number(String(str).match(/^([\d.]+)px$/)[1]);
|
||||
}
|
||||
|
||||
// Run tests simultaneously so we don't have to take up too much time.
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
var gTestsRunning = 0;
|
||||
function TestStarted() { ++gTestsRunning; }
|
||||
function TestFinished() { if (--gTestsRunning == 0) SimpleTest.finish(); }
|
||||
|
||||
// An array of arrays of functions to be called at the outer index number
|
||||
// of seconds after the present.
|
||||
var gFutureCalls = [];
|
||||
|
||||
function add_future_call(index, func)
|
||||
{
|
||||
if (!(index in gFutureCalls)) {
|
||||
gFutureCalls[index] = [];
|
||||
}
|
||||
gFutureCalls[index].push(func);
|
||||
TestStarted();
|
||||
}
|
||||
var gStartTime1, gStartTime2;
|
||||
var gCurrentTime;
|
||||
function process_future_calls(index)
|
||||
{
|
||||
var calls = gFutureCalls[index];
|
||||
if (!calls)
|
||||
return;
|
||||
gCurrentTime = Date.now();
|
||||
for (var i = 0; i < calls.length; ++i) {
|
||||
calls[i]();
|
||||
TestFinished();
|
||||
}
|
||||
}
|
||||
|
||||
function bezier(x1, y1, x2, y2) {
|
||||
// Cubic bezier with control points (0, 0), (x1, y1), (x2, y2), and (1, 1).
|
||||
function x_for_t(t) {
|
||||
var omt = 1-t;
|
||||
return 3 * omt * omt * t * x1 + 3 * omt * t * t * x2 + t * t * t;
|
||||
}
|
||||
function y_for_t(t) {
|
||||
var omt = 1-t;
|
||||
return 3 * omt * omt * t * y1 + 3 * omt * t * t * y2 + t * t * t;
|
||||
}
|
||||
function t_for_x(x) {
|
||||
// Binary subdivision.
|
||||
var mint = 0, maxt = 1;
|
||||
for (var i = 0; i < 30; ++i) {
|
||||
var guesst = (mint + maxt) / 2;
|
||||
var guessx = x_for_t(guesst);
|
||||
if (x < guessx)
|
||||
maxt = guesst;
|
||||
else
|
||||
mint = guesst;
|
||||
}
|
||||
return (mint + maxt) / 2;
|
||||
}
|
||||
return function bezier_closure(x) {
|
||||
if (x == 0) return 0;
|
||||
if (x == 1) return 1;
|
||||
return y_for_t(t_for_x(x));
|
||||
}
|
||||
}
|
||||
|
||||
var timingFunctions = {
|
||||
// a map from the value of 'transition-timing-function' to an array of
|
||||
// the portions this function yields at 0 (always 0), 1/4, 1/2, and
|
||||
// 3/4 and all (always 1) of the way through the time of the
|
||||
// transition. Each portion is represented as a value and an
|
||||
// acceptable error tolerance (based on a time error of 1%) for that
|
||||
// value.
|
||||
|
||||
// ease
|
||||
"ease": bezier(0.25, 0.1, 0.25, 1),
|
||||
"cubic-bezier(0.25, 0.1, 0.25, 1.0)": bezier(0.25, 0.1, 0.25, 1),
|
||||
|
||||
// linear and various synonyms for it
|
||||
"linear": function(x) { return x; },
|
||||
"cubic-bezier(0.0, 0.0, 1.0, 1.0)": function(x) { return x; },
|
||||
"cubic-bezier(0, 0, 1, 1)": function(x) { return x; },
|
||||
"cubic-bezier(0, 0, 0, 0.0)": function(x) { return x; },
|
||||
"cubic-bezier(1.0, 1, 0, 0)": function(x) { return x; },
|
||||
|
||||
// ease-in
|
||||
"ease-in": bezier(0.42, 0, 1, 1),
|
||||
"cubic-bezier(0.42, 0, 1.0, 1.0)": bezier(0.42, 0, 1, 1),
|
||||
|
||||
// ease-out
|
||||
"ease-out": bezier(0, 0, 0.58, 1),
|
||||
"cubic-bezier(0, 0, 0.58, 1.0)": bezier(0, 0, 0.58, 1),
|
||||
|
||||
// ease-in-out
|
||||
"ease-in-out": bezier(0.42, 0, 0.58, 1),
|
||||
"cubic-bezier(0.42, 0, 0.58, 1.0)": bezier(0.42, 0, 0.58, 1),
|
||||
|
||||
// other cubic-bezier values
|
||||
"cubic-bezier(0.4, 0.1, 0.7, 0.95)": bezier(0.4, 0.1, 0.7, 0.95),
|
||||
"cubic-bezier(1, 0, 0, 1)": bezier(1, 0, 0, 1),
|
||||
"cubic-bezier(0, 1, 1, 0)": bezier(0, 1, 1, 0),
|
||||
|
||||
};
|
||||
|
||||
var div = document.getElementById("display");
|
||||
|
||||
// Set up all the elements on which we are going to start transitions.
|
||||
|
||||
// Test all timing functions using a set of 8-second transitions, which
|
||||
// we check at times 0, 2s, 4s, 6s, and 8s.
|
||||
var tftests = [];
|
||||
for (var tf in timingFunctions) {
|
||||
var p = document.createElement("p");
|
||||
var t = document.createTextNode("transition-timing-function: " + tf);
|
||||
p.appendChild(t);
|
||||
p.style.textIndent = "0px";
|
||||
p.style.MozTransition = "8s text-indent linear";
|
||||
p.style.MozTransitionTimingFunction = tf;
|
||||
div.appendChild(p);
|
||||
is(getComputedStyle(p, "").textIndent, "0px",
|
||||
"should be zero before changing value");
|
||||
tftests.push([ p, tf ]);
|
||||
}
|
||||
|
||||
// Check that the timing function continues even when we restyle in the
|
||||
// middle.
|
||||
var interrupt_tests = [];
|
||||
for (var itime = 2; itime < 8; itime += 2) {
|
||||
var p = document.createElement("p");
|
||||
var t = document.createTextNode("interrupt at " + itime + "s");
|
||||
p.appendChild(t);
|
||||
p.style.textIndent = "0px";
|
||||
p.style.MozTransition = "8s text-indent cubic-bezier(0, 1, 1, 0)";
|
||||
div.appendChild(p);
|
||||
is(getComputedStyle(p, "").textIndent, "0px",
|
||||
"should be zero before changing value");
|
||||
interrupt_tests.push([ p, itime ]);
|
||||
|
||||
}
|
||||
setTimeout("interrupt_tests[0][0].style.color = 'blue';" +
|
||||
"check_interrupt_tests()", 2000);
|
||||
setTimeout("interrupt_tests[1][0].style.color = 'blue';" +
|
||||
"check_interrupt_tests()", 4000);
|
||||
setTimeout("interrupt_tests[2][0].style.color = 'blue';" +
|
||||
"check_interrupt_tests()", 6000);
|
||||
|
||||
// Test transition-delay values of -4s through 4s on a 4s transition
|
||||
// with 'ease-out' timing function.
|
||||
var delay_tests = {};
|
||||
for (var d = -4; d <= 4; ++d) {
|
||||
var p = document.createElement("p");
|
||||
var delay = d + "s";
|
||||
var t = document.createTextNode("transition-delay: " + delay);
|
||||
p.appendChild(t);
|
||||
p.style.marginLeft = "0px";
|
||||
p.style.MozTransition = "4s margin-left ease-out " + delay;
|
||||
div.appendChild(p);
|
||||
is(getComputedStyle(p, "").marginLeft, "0px",
|
||||
"should be zero before changing value");
|
||||
delay_tests[d] = p;
|
||||
}
|
||||
|
||||
// Test that changing the value on an already-running transition to the
|
||||
// value it currently happens to have resets the transition.
|
||||
var p = document.createElement("p");
|
||||
var t = document.createTextNode("transition-delay reset to starting point");
|
||||
p.appendChild(t);
|
||||
p.style.marginLeft = "0px";
|
||||
p.style.MozTransition = "4s margin-left ease-out 4s";
|
||||
div.appendChild(p);
|
||||
is(getComputedStyle(p, "").marginLeft, "0px",
|
||||
"should be zero before changing value");
|
||||
var reset_test = p;
|
||||
|
||||
// Test that transitions on descendants do not trigger when the
|
||||
// inherited value is itself transitioning. In other words, when
|
||||
// ancestor and descendant both have a transition for the same property,
|
||||
// and the descendant inherits the property from the ancestor, the
|
||||
// descendant's transition is ignored (as part of the idea of not
|
||||
// starting transitions on changes that result from animation).
|
||||
// See http://lists.w3.org/Archives/Public/www-style/2009Jun/0121.html
|
||||
// and http://lists.w3.org/Archives/Public/www-style/2009Jul/0050.html
|
||||
var descendant_tests = [
|
||||
{ parent_transition: "",
|
||||
child_transition: "4s text-indent" },
|
||||
{ parent_transition: "4s text-indent",
|
||||
child_transition: "" },
|
||||
{ parent_transition: "4s text-indent",
|
||||
child_transition: "16s text-indent" },
|
||||
{ parent_transition: "4s text-indent",
|
||||
child_transition: "1s text-indent" },
|
||||
{ parent_transition: "8s letter-spacing",
|
||||
child_transition: "4s text-indent" },
|
||||
{ parent_transition: "4s text-indent",
|
||||
child_transition: "8s letter-spacing" },
|
||||
{ parent_transition: "4s text-indent",
|
||||
child_transition: "8s all" },
|
||||
{ parent_transition: "8s text-indent",
|
||||
child_transition: "4s all" },
|
||||
// examples with positive and negative delay
|
||||
{ parent_transition: "4s text-indent 1s",
|
||||
child_transition: "8s text-indent" },
|
||||
{ parent_transition: "4s text-indent -1s",
|
||||
child_transition: "8s text-indent" }
|
||||
];
|
||||
|
||||
for (var i in descendant_tests) {
|
||||
var test = descendant_tests[i];
|
||||
test.parentNode = document.createElement("div");
|
||||
test.childNode = document.createElement("p");
|
||||
test.parentNode.appendChild(test.childNode);
|
||||
test.childNode.appendChild(document.createTextNode(
|
||||
"parent with \"" + test.parent_transition + "\" and " +
|
||||
"child with \"" + test.child_transition + "\""));
|
||||
test.parentNode.style.MozTransition = test.parent_transition;
|
||||
test.childNode.style.MozTransition = test.child_transition;
|
||||
test.parentNode.style.textIndent = "50px"; // transition from 50 to 150
|
||||
test.parentNode.style.letterSpacing = "10px"; // transition from 10 to 5
|
||||
div.appendChild(test.parentNode);
|
||||
var parentCS = getComputedStyle(test.parentNode, "");
|
||||
var childCS = getComputedStyle(test.childNode, "");
|
||||
is(parentCS.textIndent, "50px",
|
||||
"parent text-indent should be 50px before changing");
|
||||
is(parentCS.letterSpacing, "10px",
|
||||
"parent letter-spacing should be 10px before changing");
|
||||
is(childCS.textIndent, "50px",
|
||||
"child text-indent should be 50px before changing");
|
||||
is(childCS.letterSpacing, "10px",
|
||||
"child letter-spacing should be 10px before changing");
|
||||
test.childCS = childCS;
|
||||
}
|
||||
|
||||
// For all of these transitions, the transition for margin-left should
|
||||
// have a duration of 8s, and the default timing function (ease) and
|
||||
// delay (0).
|
||||
// This is because we're implementing the proposal in
|
||||
// http://lists.w3.org/Archives/Public/www-style/2009Aug/0109.html
|
||||
var number_tests = [
|
||||
{ style: "-moz-transition: 4s margin, 8s margin-left" },
|
||||
{ style: "-moz-transition: 4s margin-left, 8s margin" },
|
||||
{ style: "-moz-transition-property: margin-left; " +
|
||||
"-moz-transition-duration: 8s, 2s" },
|
||||
{ style: "-moz-transition-property: margin-left, margin-left; " +
|
||||
"-moz-transition-duration: 2s, 8s" },
|
||||
{ style: "-moz-transition-property: margin-left, margin-left, margin-left; " +
|
||||
"-moz-transition-duration: 8s, 2s" },
|
||||
{ style: "-moz-transition-property: margin-left; " +
|
||||
"-moz-transition-duration: 8s, 16s" },
|
||||
{ style: "-moz-transition-property: margin-left, margin-left; " +
|
||||
"-moz-transition-duration: 16s, 8s" },
|
||||
{ style: "-moz-transition-property: margin-left, margin-left, margin-left; " +
|
||||
"-moz-transition-duration: 8s, 16s" },
|
||||
{ style: "-moz-transition-property: text-indent,word-spacing,margin-left; " +
|
||||
"-moz-transition-duration: 8s; " +
|
||||
"-moz-transition-delay: 0, 8s" },
|
||||
{ style: "-moz-transition-property: text-indent,word-spacing,margin-left; " +
|
||||
"-moz-transition-duration: 8s, 16s; " +
|
||||
"-moz-transition-delay: 8s, 8s, 0, 8s, 8s, 8s" },
|
||||
];
|
||||
|
||||
for (var i in number_tests) {
|
||||
var test = number_tests[i];
|
||||
var p = document.createElement("p");
|
||||
p.setAttribute("style", test.style);
|
||||
var t = document.createTextNode(test.style);
|
||||
p.appendChild(t);
|
||||
p.style.marginLeft = "100px";
|
||||
div.appendChild(p);
|
||||
is(getComputedStyle(p, "").marginLeft, "100px",
|
||||
"should be 100px before changing value");
|
||||
test.node = p;
|
||||
}
|
||||
|
||||
// Test transitions that are also from-display:none, to-display:none, and
|
||||
// display:none throughout.
|
||||
var from_none_test, to_none_test, always_none_test;
|
||||
function make_display_test(initially_none, text)
|
||||
{
|
||||
var p = document.createElement("p");
|
||||
p.appendChild(document.createTextNode(text));
|
||||
p.style.textIndent = "0px";
|
||||
p.style.MozTransition = "8s text-indent ease-in-out";
|
||||
if (initially_none)
|
||||
p.style.display = "none";
|
||||
div.appendChild(p);
|
||||
return p;
|
||||
}
|
||||
from_none_test = make_display_test(true, "transition from display:none");
|
||||
to_none_test = make_display_test(false, "transition to display:none");
|
||||
always_none_test = make_display_test(true, "transition always display:none");
|
||||
var display_tests = [ from_none_test, to_none_test, always_none_test ];
|
||||
|
||||
// FIXME: Test a transition that reverses partway through.
|
||||
|
||||
// flush style changes
|
||||
var x = getComputedStyle(div, "").color;
|
||||
|
||||
// Start our timer as close as possible to when we start the first
|
||||
// transition.
|
||||
// Do not use setInterval because once it gets off in time, it stays off.
|
||||
for (var i = 1; i <= 8; ++i) {
|
||||
setTimeout(process_future_calls, i * 1000, i);
|
||||
}
|
||||
gStartTime1 = Date.now(); // set before any transitions have started
|
||||
|
||||
// Start all the transitions.
|
||||
for (var test in tftests) {
|
||||
var p = tftests[test][0];
|
||||
p.style.textIndent = "100px";
|
||||
}
|
||||
for (var test in interrupt_tests) {
|
||||
var p = interrupt_tests[test][0];
|
||||
p.style.textIndent = "100px";
|
||||
}
|
||||
for (var d in delay_tests) {
|
||||
var p = delay_tests[d];
|
||||
p.style.marginLeft = "100px";
|
||||
}
|
||||
reset_test.style.marginLeft = "100px";
|
||||
for (var i in descendant_tests) {
|
||||
var test = descendant_tests[i];
|
||||
test.parentNode.style.textIndent = "150px";
|
||||
test.parentNode.style.letterSpacing = "5px";
|
||||
}
|
||||
for (var i in number_tests) {
|
||||
var test = number_tests[i];
|
||||
test.node.style.marginLeft = "50px";
|
||||
}
|
||||
from_none_test.style.textIndent = "100px";
|
||||
from_none_test.style.display = "";
|
||||
to_none_test.style.textIndent = "100px";
|
||||
to_none_test.style.display = "none";
|
||||
always_none_test.style.textIndent = "100px";
|
||||
|
||||
// flush style changes
|
||||
x = getComputedStyle(div, "").color;
|
||||
|
||||
gStartTime2 = Date.now(); // set after all transitions have started
|
||||
gCurrentTime = gStartTime2;
|
||||
|
||||
/**
|
||||
* Assert that a transition whose timing function yields the bezier
|
||||
* |func|, running from |start_time| to |end_time| (both in seconds
|
||||
* relative to when the transitions were started) should have produced
|
||||
* computed value |cval| given that the transition was from
|
||||
* |start_value| to |end_value| (both numbers in CSS pixels).
|
||||
*/
|
||||
function check_transition_value(func, start_time, end_time,
|
||||
start_value, end_value, cval, desc,
|
||||
xfail)
|
||||
{
|
||||
function value_at(elapsed, error_portion) {
|
||||
var time_portion = (elapsed - start_time) / (end_time - start_time);
|
||||
if (time_portion < 0)
|
||||
time_portion = 0;
|
||||
else if (time_portion > 1)
|
||||
time_portion = 1;
|
||||
var value_portion = func(time_portion) + error_portion;
|
||||
if (value_portion < 0)
|
||||
value_portion = 0;
|
||||
else if (value_portion > 1)
|
||||
value_portion = 1;
|
||||
return (1 - value_portion) * start_value + value_portion * end_value;
|
||||
}
|
||||
|
||||
var time_range; // in seconds
|
||||
var uns_range; // |range| before being sorted (so errors give it
|
||||
// in the original order
|
||||
if (gCurrentTime == gStartTime2) {
|
||||
// No timers involved
|
||||
time_range = [0, 0];
|
||||
if (start_time < 0) {
|
||||
uns_range = [ value_at(0, -0.01), value_at(0, 0.01) ];
|
||||
} else {
|
||||
var val = value_at(0, 0);
|
||||
uns_range = [val, val];
|
||||
}
|
||||
} else {
|
||||
// seconds
|
||||
// FIXME: Why do we need so much tolerance at the low end of the
|
||||
// range (primarily for Mac)?
|
||||
time_range = [ (gCurrentTime - gStartTime2 - 40) / 1000,
|
||||
(Date.now() - gStartTime1 + 20) / 1000 ];
|
||||
uns_range = [ value_at(time_range[0], -0.01),
|
||||
value_at(time_range[1], 0.01) ];
|
||||
|
||||
}
|
||||
var range = uns_range.concat(). /* concat to clone array */
|
||||
sort(function compareNumbers(a,b) { return a - b; });
|
||||
var actual = px_to_num(cval);
|
||||
|
||||
var fn = xfail ? todo : ok;
|
||||
|
||||
fn(range[0] <= actual && actual <= range[1],
|
||||
desc + ": computed value " + cval + " should be between " +
|
||||
uns_range[0].toFixed(6) + "px and " + uns_range[1].toFixed(6) +
|
||||
"px at time between " + time_range[0] + "s and " + time_range[1] + "s.");
|
||||
}
|
||||
|
||||
function check_tf_test()
|
||||
{
|
||||
for (var test in tftests) {
|
||||
var p = tftests[test][0];
|
||||
var tf = tftests[test][1];
|
||||
|
||||
check_transition_value(timingFunctions[tf], 0, 8, 0, 100,
|
||||
getComputedStyle(p, "").textIndent,
|
||||
"timing function test for timing function " + tf);
|
||||
|
||||
}
|
||||
|
||||
check_interrupt_tests();
|
||||
}
|
||||
|
||||
check_tf_test();
|
||||
add_future_call(2, check_tf_test);
|
||||
add_future_call(4, check_tf_test);
|
||||
add_future_call(6, check_tf_test);
|
||||
add_future_call(8, check_tf_test);
|
||||
|
||||
function check_interrupt_tests()
|
||||
{
|
||||
for (var test in interrupt_tests) {
|
||||
var p = interrupt_tests[test][0];
|
||||
var itime = interrupt_tests[test][1];
|
||||
|
||||
check_transition_value(timingFunctions["cubic-bezier(0, 1, 1, 0)"],
|
||||
0, 8, 0, 100,
|
||||
getComputedStyle(p, "").textIndent,
|
||||
"interrupt test for time " + itime + "s");
|
||||
}
|
||||
}
|
||||
|
||||
// check_interrupt_tests is called from check_tf_test and from
|
||||
// where we reset the interrupts
|
||||
|
||||
function check_delay_test(time)
|
||||
{
|
||||
var tf = timingFunctions["ease-out"];
|
||||
for (var d in delay_tests) {
|
||||
var p = delay_tests[d];
|
||||
|
||||
check_transition_value(tf, Number(d), Number(d) + 4, 0, 100,
|
||||
getComputedStyle(p, "").marginLeft,
|
||||
"delay test for delay " + d + "s");
|
||||
}
|
||||
}
|
||||
|
||||
check_delay_test(0);
|
||||
for (var i = 1; i <= 8; ++i) {
|
||||
add_future_call(i, check_delay_test);
|
||||
}
|
||||
|
||||
function reset_reset_test(time)
|
||||
{
|
||||
reset_test.style.marginLeft = "0px";
|
||||
}
|
||||
function check_reset_test(time)
|
||||
{
|
||||
is(getComputedStyle(reset_test, "").marginLeft, "0px",
|
||||
"reset test value at time " + time + "s.");
|
||||
}
|
||||
setTimeout(reset_reset_test, 1000); // must always run, even if late
|
||||
check_reset_test(0);
|
||||
for (var i = 1; i <= 8; ++i) {
|
||||
(function(j) {
|
||||
add_future_call(j, function() { check_reset_test(j); });
|
||||
})(i);
|
||||
}
|
||||
|
||||
check_descendant_tests();
|
||||
add_future_call(2, check_descendant_tests);
|
||||
add_future_call(6, check_descendant_tests);
|
||||
|
||||
function check_descendant_tests() {
|
||||
// text-indent: transition from 50px to 150px
|
||||
// letter-spacing: transition from 10px to 5px
|
||||
var values = {};
|
||||
values["text-indent"] = [ 50, 150 ];
|
||||
values["letter-spacing"] = [ 10, 5 ];
|
||||
var tf = timingFunctions["ease"];
|
||||
|
||||
for (var i in descendant_tests) {
|
||||
var test = descendant_tests[i];
|
||||
|
||||
/* ti=text-indent, ls=letter-spacing */
|
||||
var child_ti_duration = 0;
|
||||
var child_ls_duration = 0;
|
||||
var child_ti_delay = 0;
|
||||
var child_ls_delay = 0;
|
||||
|
||||
if (test.parent_transition != "") {
|
||||
var props = test.parent_transition.split(" ");
|
||||
var duration = parseInt(props[0]);
|
||||
var delay = (props.length > 2) ? parseInt(props[2]) : 0;
|
||||
var property = props[1];
|
||||
if (property == "text-indent") {
|
||||
child_ti_duration = duration;
|
||||
child_ti_delay = delay;
|
||||
} else if (property == "letter-spacing") {
|
||||
child_ls_duration = duration;
|
||||
child_ls_delay = delay;
|
||||
} else {
|
||||
ok(false, "fix this test (unexpected transition-property " +
|
||||
property + " on parent)");
|
||||
}
|
||||
}
|
||||
|
||||
if (test.child_transition != "") {
|
||||
var props = test.child_transition.split(" ");
|
||||
var duration = parseInt(props[0]);
|
||||
var delay = (props.length > 2) ? parseInt(props[2]) : 0;
|
||||
var property = props[1];
|
||||
if (property != "text-indent" && property != "letter-spacing" &&
|
||||
property != "all") {
|
||||
ok(false, "fix this test (unexpected transition-property " +
|
||||
property + " on child)");
|
||||
}
|
||||
|
||||
if (property != "letter-spacing" && child_ti_duration == 0) {
|
||||
child_ti_duration = duration;
|
||||
child_ti_delay = delay;
|
||||
}
|
||||
if (property != "text-indent" && child_ls_duration == 0) {
|
||||
child_ls_duration = duration;
|
||||
child_ls_delay = delay;
|
||||
}
|
||||
}
|
||||
|
||||
var time_portions = {
|
||||
"text-indent":
|
||||
{ duration: child_ti_duration, delay: child_ti_delay },
|
||||
"letter-spacing":
|
||||
{ duration: child_ls_duration, delay: child_ls_delay },
|
||||
};
|
||||
|
||||
for (var prop in {"text-indent": true, "letter-spacing": true}) {
|
||||
var time_portion = time_portions[prop];
|
||||
|
||||
if (time_portion.duration == 0) {
|
||||
time_portion.duration = 0.01;
|
||||
time_portion.delay = -1;
|
||||
}
|
||||
|
||||
check_transition_value(tf, time_portion.delay,
|
||||
time_portion.delay + time_portion.duration,
|
||||
values[prop][0], values[prop][1],
|
||||
test.childCS.getPropertyValue(prop),
|
||||
"descendant test, property " + prop);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function check_number_tests()
|
||||
{
|
||||
var tf = timingFunctions["ease"];
|
||||
for (var d in number_tests) {
|
||||
var test = number_tests[d];
|
||||
var p = test.node;
|
||||
|
||||
check_transition_value(tf, 0, 8, 100, 50,
|
||||
getComputedStyle(p, "").marginLeft,
|
||||
"number of transitions test for style " +
|
||||
test.style);
|
||||
}
|
||||
}
|
||||
|
||||
check_number_tests(0);
|
||||
add_future_call(2, check_number_tests);
|
||||
add_future_call(4, check_number_tests);
|
||||
add_future_call(6, check_number_tests);
|
||||
add_future_call(8, check_number_tests);
|
||||
|
||||
function check_display_tests(time)
|
||||
{
|
||||
var tf = timingFunctions["ease-in-out"];
|
||||
for (var i in display_tests) {
|
||||
var p = display_tests[i];
|
||||
|
||||
check_transition_value(tf, 0, 8, 0, 100,
|
||||
getComputedStyle(p, "").textIndent,
|
||||
"display test for test with " +
|
||||
p.childNodes[0].data,
|
||||
// TODO: Making transitions work on 'display:none' elements is
|
||||
// still not implemented.
|
||||
(time != 8));
|
||||
}
|
||||
}
|
||||
|
||||
check_display_tests(0);
|
||||
add_future_call(2, function() { check_display_tests(2); });
|
||||
add_future_call(4, function() { check_display_tests(4); });
|
||||
add_future_call(6, function() { check_display_tests(6); });
|
||||
add_future_call(8, function() { check_display_tests(8); });
|
||||
|
||||
</script>
|
||||
</pre>
|
||||
</body>
|
||||
</html>
|
@ -211,6 +211,18 @@ public:
|
||||
NS_ASSERTION(!aOther.IsNull(), "Cannot compute with aOther null value");
|
||||
return mValue > aOther.mValue;
|
||||
}
|
||||
PRBool operator==(const TimeStamp& aOther) const {
|
||||
// Maybe it's ok to check == with null timestamps?
|
||||
NS_ASSERTION(!IsNull(), "Cannot compute with a null value");
|
||||
NS_ASSERTION(!aOther.IsNull(), "Cannot compute with aOther null value");
|
||||
return mValue == aOther.mValue;
|
||||
}
|
||||
PRBool operator!=(const TimeStamp& aOther) const {
|
||||
// Maybe it's ok to check != with null timestamps?
|
||||
NS_ASSERTION(!IsNull(), "Cannot compute with a null value");
|
||||
NS_ASSERTION(!aOther.IsNull(), "Cannot compute with aOther null value");
|
||||
return mValue != aOther.mValue;
|
||||
}
|
||||
|
||||
// Comparing TimeStamps for equality should be discouraged. Adding
|
||||
// two TimeStamps, or scaling TimeStamps, is nonsense and must never
|
||||
|
Loading…
x
Reference in New Issue
Block a user