diff --git a/content/base/src/nsGenericElement.cpp b/content/base/src/nsGenericElement.cpp index 6bb8297bb030..4b406b8c1b8f 100644 --- a/content/base/src/nsGenericElement.cpp +++ b/content/base/src/nsGenericElement.cpp @@ -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); diff --git a/content/base/src/nsGkAtomList.h b/content/base/src/nsGkAtomList.h index e9636a19832d..1a5ca30fc258 100644 --- a/content/base/src/nsGkAtomList.h +++ b/content/base/src/nsGkAtomList.h @@ -1674,6 +1674,9 @@ GK_ATOM(preTransformBBoxProperty, "PreTransformBBoxProperty") // nsRect* GK_ATOM(rowUnpaginatedHeightProperty, "RowUnpaginatedHeightProperty") // nscoord* GK_ATOM(tabWidthProperty, "TabWidthProperty") // nsTArray* 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") diff --git a/content/smil/Makefile.in b/content/smil/Makefile.in index 5eed0f609f60..c9fa429c6a76 100644 --- a/content/smil/Makefile.in +++ b/content/smil/Makefile.in @@ -81,6 +81,7 @@ EXPORTS = \ nsISMILAttr.h \ nsSMILAnimationController.h \ nsSMILCompositorTable.h \ + nsSMILKeySpline.h \ nsSMILTimeContainer.h \ nsSMILTypes.h \ $(NULL) diff --git a/content/smil/nsSMILKeySpline.cpp b/content/smil/nsSMILKeySpline.cpp index de2d920853d2..47583de1b965 100644 --- a/content/smil/nsSMILKeySpline.cpp +++ b/content/smil/nsSMILKeySpline.cpp @@ -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(); } diff --git a/content/smil/nsSMILKeySpline.h b/content/smil/nsSMILKeySpline.h index 337247f6257d..a2edf3d53206 100644 --- a/content/smil/nsSMILKeySpline.h +++ b/content/smil/nsSMILKeySpline.h @@ -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]; diff --git a/layout/base/Makefile.in b/layout/base/Makefile.in index 5fddcd0ec783..1aa50168e19a 100644 --- a/layout/base/Makefile.in +++ b/layout/base/Makefile.in @@ -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) diff --git a/layout/base/nsCSSFrameConstructor.h b/layout/base/nsCSSFrameConstructor.h index a503c9f65b65..4d9d57b53648 100644 --- a/layout/base/nsCSSFrameConstructor.h +++ b/layout/base/nsCSSFrameConstructor.h @@ -68,6 +68,7 @@ class nsIFrame; struct nsGenConInitializer; class ChildIterator; class nsICSSAnonBoxPseudo; +class nsPageContentFrame; struct nsFindFrameHint { diff --git a/layout/base/nsFrameManager.cpp b/layout/base/nsFrameManager.cpp index a8efcf804e14..ad1f6b6dc443 100644 --- a/layout/base/nsFrameManager.cpp +++ b/layout/base/nsFrameManager.cpp @@ -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 *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 coverRule = + aPresContext->TransitionManager()->StyleContextChanged( + aContent, aOldStyleContext, *aNewStyleContext); + if (coverRule) { + nsCOMArray 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); diff --git a/layout/base/nsPresContext.cpp b/layout/base/nsPresContext.cpp index 8ced7aef0b0e..b7b8f32f11fd 100644 --- a/layout/base/nsPresContext.cpp +++ b/layout/base/nsPresContext.cpp @@ -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 diff --git a/layout/base/nsPresContext.h b/layout/base/nsPresContext.h index 4a9d56a90fe4..b3da1075f056 100644 --- a/layout/base/nsPresContext.h +++ b/layout/base/nsPresContext.h @@ -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( + reinterpret_cast(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 diff --git a/layout/base/nsRefreshDriver.cpp b/layout/base/nsRefreshDriver.cpp new file mode 100644 index 000000000000..e6c36da572dd --- /dev/null +++ b/layout/base/nsRefreshDriver.cpp @@ -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 , 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(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(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 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; +} diff --git a/layout/base/nsRefreshDriver.h b/layout/base/nsRefreshDriver.h new file mode 100644 index 000000000000..d767b99cb85d --- /dev/null +++ b/layout/base/nsRefreshDriver.h @@ -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 , 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 ObserverArray; + + void EnsureTimerStarted(); + void StopTimer(); + PRUint32 ObserverCount() const; + void UpdateMostRecentRefresh(); + ObserverArray& ArrayFor(mozFlushType aFlushType); + + nsCOMPtr 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_) */ diff --git a/layout/reftests/css-transitions/reftest.list b/layout/reftests/css-transitions/reftest.list new file mode 100644 index 000000000000..f80d08cdff59 --- /dev/null +++ b/layout/reftests/css-transitions/reftest.list @@ -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 diff --git a/layout/reftests/css-transitions/transitions-inline-already-wrapped-1.html b/layout/reftests/css-transitions/transitions-inline-already-wrapped-1.html new file mode 100644 index 000000000000..f62f238d06ef --- /dev/null +++ b/layout/reftests/css-transitions/transitions-inline-already-wrapped-1.html @@ -0,0 +1,30 @@ + +Test for CSS transitions and re-wrapping of inlines + + +
+ +This is some text with a transition. + +
diff --git a/layout/reftests/css-transitions/transitions-inline-already-wrapped-2.html b/layout/reftests/css-transitions/transitions-inline-already-wrapped-2.html new file mode 100644 index 000000000000..aca1fe7a4265 --- /dev/null +++ b/layout/reftests/css-transitions/transitions-inline-already-wrapped-2.html @@ -0,0 +1,28 @@ + +Test for CSS transitions and re-wrapping of inlines + + +
+ +This is some text with a transition. + +
diff --git a/layout/reftests/css-transitions/transitions-inline-ref.html b/layout/reftests/css-transitions/transitions-inline-ref.html new file mode 100644 index 000000000000..16ade1cfe787 --- /dev/null +++ b/layout/reftests/css-transitions/transitions-inline-ref.html @@ -0,0 +1,7 @@ + +Test for CSS transitions and re-wrapping of inlines +
+ +This is some text with a transition. + +
diff --git a/layout/reftests/css-transitions/transitions-inline-rewrap-1.html b/layout/reftests/css-transitions/transitions-inline-rewrap-1.html new file mode 100644 index 000000000000..e424bf5f0876 --- /dev/null +++ b/layout/reftests/css-transitions/transitions-inline-rewrap-1.html @@ -0,0 +1,33 @@ + +Test for CSS transitions and re-wrapping of inlines + + +
+ +This is some text with a transition. + +
diff --git a/layout/reftests/css-transitions/transitions-inline-rewrap-2.html b/layout/reftests/css-transitions/transitions-inline-rewrap-2.html new file mode 100644 index 000000000000..2191ad19d3cf --- /dev/null +++ b/layout/reftests/css-transitions/transitions-inline-rewrap-2.html @@ -0,0 +1,31 @@ + +Test for CSS transitions and re-wrapping of inlines + + +
+ +This is some text with a transition. + +
diff --git a/layout/reftests/reftest.list b/layout/reftests/reftest.list index 7a84b89c3d9c..1247025947c0 100644 --- a/layout/reftests/reftest.list +++ b/layout/reftests/reftest.list @@ -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 diff --git a/layout/style/Makefile.in b/layout/style/Makefile.in index 3d3cf67896c1..045ccdad6355 100644 --- a/layout/style/Makefile.in +++ b/layout/style/Makefile.in @@ -141,6 +141,7 @@ CPPSRCS = \ nsStyleStruct.cpp \ nsStyleTransformMatrix.cpp \ nsStyleUtil.cpp \ + nsTransitionManager.cpp \ $(NULL) FORCE_STATIC_LIB = 1 diff --git a/layout/style/nsStyleSet.cpp b/layout/style/nsStyleSet.cpp index b68396bc14b9..9c726f6838ea 100644 --- a/layout/style/nsStyleSet.cpp +++ b/layout/style/nsStyleSet.cpp @@ -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) diff --git a/layout/style/nsStyleSet.h b/layout/style/nsStyleSet.h index c9fb9264da2c..cc32a3adc15f 100644 --- a/layout/style/nsStyleSet.h +++ b/layout/style/nsStyleSet.h @@ -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 }; diff --git a/layout/style/nsStyleStruct.cpp b/layout/style/nsStyleStruct.cpp index f4f20b3f11bf..aa339734c032 100644 --- a/layout/style/nsStyleStruct.cpp +++ b/layout/style/nsStyleStruct.cpp @@ -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; } diff --git a/layout/style/nsTransitionManager.cpp b/layout/style/nsTransitionManager.cpp new file mode 100644 index 000000000000..a4870f1b1475 --- /dev/null +++ b/layout/style/nsTransitionManager.cpp @@ -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 , 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 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 mPropertyTransitions; + + // The style rule for the transitions (which contains the time stamp + // for which it is valid). + nsRefPtr 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(aPropertyValue); + delete et; +} + +NS_IMPL_ISUPPORTS1(ElementTransitionsStyleRule, nsIStyleRule) + +static void +ElementTransitionsPostResolveCallback(void* aStyleStruct, nsRuleData* aRuleData, + nsIStyleRule* aRule) +{ + ElementTransitionsStyleRule *rule = + static_cast(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(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( + PR_LIST_HEAD(&mElementTransitions)); + head->Destroy(); + } +} + +already_AddRefed +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 coverRule = + new CoverTransitionStartStyleRule; + if (!coverRule) { + NS_WARNING("out of memory"); + return nsnull; + } + + nsTArray &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( + static_cast(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::NoIndex; + if (aElementTransitions) { + nsTArray &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::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 &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::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 &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::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( + 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(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); + } +} diff --git a/layout/style/nsTransitionManager.h b/layout/style/nsTransitionManager.h new file mode 100644 index 000000000000..6cbde7d73fc8 --- /dev/null +++ b/layout/style/nsTransitionManager.h @@ -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 , 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 + 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_) */ diff --git a/layout/style/test/Makefile.in b/layout/style/test/Makefile.in index 167a6964abd0..1c0ebddfe5c7 100644 --- a/layout/style/test/Makefile.in +++ b/layout/style/test/Makefile.in @@ -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 \ diff --git a/layout/style/test/test_transitions.html b/layout/style/test/test_transitions.html new file mode 100644 index 000000000000..2f6811963541 --- /dev/null +++ b/layout/style/test/test_transitions.html @@ -0,0 +1,626 @@ + + + + + Test for Bug 435441 + + + + + + +Mozilla Bug 435441 +
+ +
+
+
+
+ + diff --git a/xpcom/ds/TimeStamp.h b/xpcom/ds/TimeStamp.h index fe1e7f651818..9d1e3d796e8e 100644 --- a/xpcom/ds/TimeStamp.h +++ b/xpcom/ds/TimeStamp.h @@ -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