summaryrefslogtreecommitdiffstats
path: root/layout/style
diff options
context:
space:
mode:
Diffstat (limited to 'layout/style')
-rw-r--r--layout/style/AnimationCommon.h20
-rw-r--r--layout/style/nsAnimationManager.cpp179
-rw-r--r--layout/style/nsAnimationManager.h27
-rw-r--r--layout/style/nsCSSKeywordList.h4
-rw-r--r--layout/style/nsCSSPropList.h11
-rw-r--r--layout/style/nsCSSProps.cpp11
-rw-r--r--layout/style/nsCSSProps.h1
-rw-r--r--layout/style/nsComputedDOMStyle.cpp10
-rw-r--r--layout/style/nsComputedDOMStyle.h1
-rw-r--r--layout/style/nsComputedDOMStylePropertyList.h1
-rw-r--r--layout/style/nsRuleNode.cpp7
-rw-r--r--layout/style/nsStyleConsts.h8
-rw-r--r--layout/style/nsStyleStruct.cpp3
-rw-r--r--layout/style/nsStyleStruct.h1
-rw-r--r--layout/style/nsTransitionManager.cpp113
-rw-r--r--layout/style/nsTransitionManager.h11
-rw-r--r--layout/style/test/property_database.js11
-rw-r--r--layout/style/test/test_animations.html3
-rw-r--r--layout/style/test/test_animations_event_handler_attribute.html40
-rw-r--r--layout/style/test/test_animations_event_order.html117
-rw-r--r--layout/style/test/test_animations_omta.html3
21 files changed, 441 insertions, 141 deletions
diff --git a/layout/style/AnimationCommon.h b/layout/style/AnimationCommon.h
index 37030411c..025c034a4 100644
--- a/layout/style/AnimationCommon.h
+++ b/layout/style/AnimationCommon.h
@@ -251,6 +251,26 @@ ImplCycleCollectionTraverse(nsCycleCollectionTraversalCallback& aCallback,
aField.Traverse(&aCallback, aName);
}
+// Return the TransitionPhase or AnimationPhase to use when the animation
+// doesn't have a target effect.
+template <typename PhaseType>
+PhaseType GetAnimationPhaseWithoutEffect(const dom::Animation& aAnimation)
+{
+ MOZ_ASSERT(!aAnimation.GetEffect(),
+ "Should only be called when we do not have an effect");
+
+ Nullable<TimeDuration> currentTime = aAnimation.GetCurrentTime();
+ if (currentTime.IsNull()) {
+ return PhaseType::Idle;
+ }
+
+ // If we don't have a target effect, the duration will be zero so the phase is
+ // 'before' if the current time is less than zero.
+ return currentTime.Value() < TimeDuration()
+ ? PhaseType::Before
+ : PhaseType::After;
+};
+
} // namespace mozilla
#endif /* !defined(mozilla_css_AnimationCommon_h) */
diff --git a/layout/style/nsAnimationManager.cpp b/layout/style/nsAnimationManager.cpp
index ed2b5afc7..aa1b6fe78 100644
--- a/layout/style/nsAnimationManager.cpp
+++ b/layout/style/nsAnimationManager.cpp
@@ -33,11 +33,15 @@ using mozilla::dom::AnimationPlayState;
using mozilla::dom::KeyframeEffectReadOnly;
using mozilla::dom::CSSAnimation;
+typedef mozilla::ComputedTiming::AnimationPhase AnimationPhase;
+
namespace {
-// Pair of an event message and elapsed time used when determining the set of
-// events to queue.
-typedef Pair<EventMessage, StickyTimeDuration> EventPair;
+struct AnimationEventParams {
+ EventMessage mMessage;
+ StickyTimeDuration mElapsedTime;
+ TimeStamp mTimeStamp;
+};
} // anonymous namespace
@@ -154,12 +158,8 @@ CSSAnimation::HasLowerCompositeOrderThan(const CSSAnimation& aOther) const
}
void
-CSSAnimation::QueueEvents()
+CSSAnimation::QueueEvents(StickyTimeDuration aActiveTime)
{
- if (!mEffect) {
- return;
- }
-
// If the animation is pending, we ignore animation events until we finish
// pending.
if (mPendingState != PendingState::NotPending) {
@@ -194,77 +194,116 @@ CSSAnimation::QueueEvents()
}
nsAnimationManager* manager = presContext->AnimationManager();
- ComputedTiming computedTiming = mEffect->GetComputedTiming();
- if (computedTiming.mPhase == ComputedTiming::AnimationPhase::Null) {
- return; // do nothing
+ const StickyTimeDuration zeroDuration;
+ uint64_t currentIteration = 0;
+ ComputedTiming::AnimationPhase currentPhase;
+ StickyTimeDuration intervalStartTime;
+ StickyTimeDuration intervalEndTime;
+ StickyTimeDuration iterationStartTime;
+
+ if (!mEffect) {
+ currentPhase = GetAnimationPhaseWithoutEffect
+ <ComputedTiming::AnimationPhase>(*this);
+ } else {
+ ComputedTiming computedTiming = mEffect->GetComputedTiming();
+ currentPhase = computedTiming.mPhase;
+ currentIteration = computedTiming.mCurrentIteration;
+ if (currentPhase == mPreviousPhase &&
+ currentIteration == mPreviousIteration) {
+ return;
+ }
+ intervalStartTime =
+ std::max(std::min(StickyTimeDuration(-mEffect->SpecifiedTiming().mDelay),
+ computedTiming.mActiveDuration),
+ zeroDuration);
+ intervalEndTime =
+ std::max(std::min((EffectEnd() - mEffect->SpecifiedTiming().mDelay),
+ computedTiming.mActiveDuration),
+ zeroDuration);
+
+ uint64_t iterationBoundary = mPreviousIteration > currentIteration
+ ? currentIteration + 1
+ : currentIteration;
+ iterationStartTime =
+ computedTiming.mDuration.MultDouble(
+ (iterationBoundary - computedTiming.mIterationStart));
}
- // Note that script can change the start time, so we have to handle moving
- // backwards through the animation as well as forwards. An 'animationstart'
- // is dispatched if we enter the active phase (regardless if that is from
- // before or after the animation's active phase). An 'animationend' is
- // dispatched if we leave the active phase (regardless if that is to before
- // or after the animation's active phase).
-
- bool wasActive = mPreviousPhaseOrIteration != PREVIOUS_PHASE_BEFORE &&
- mPreviousPhaseOrIteration != PREVIOUS_PHASE_AFTER;
- bool isActive =
- computedTiming.mPhase == ComputedTiming::AnimationPhase::Active;
- bool isSameIteration =
- computedTiming.mCurrentIteration == mPreviousPhaseOrIteration;
- bool skippedActivePhase =
- (mPreviousPhaseOrIteration == PREVIOUS_PHASE_BEFORE &&
- computedTiming.mPhase == ComputedTiming::AnimationPhase::After) ||
- (mPreviousPhaseOrIteration == PREVIOUS_PHASE_AFTER &&
- computedTiming.mPhase == ComputedTiming::AnimationPhase::Before);
- bool skippedFirstIteration =
- isActive &&
- mPreviousPhaseOrIteration == PREVIOUS_PHASE_BEFORE &&
- computedTiming.mCurrentIteration > 0;
-
- MOZ_ASSERT(!skippedActivePhase || (!isActive && !wasActive),
- "skippedActivePhase only makes sense if we were & are inactive");
-
- if (computedTiming.mPhase == ComputedTiming::AnimationPhase::Before) {
- mPreviousPhaseOrIteration = PREVIOUS_PHASE_BEFORE;
- } else if (computedTiming.mPhase == ComputedTiming::AnimationPhase::Active) {
- mPreviousPhaseOrIteration = computedTiming.mCurrentIteration;
- } else if (computedTiming.mPhase == ComputedTiming::AnimationPhase::After) {
- mPreviousPhaseOrIteration = PREVIOUS_PHASE_AFTER;
+ TimeStamp startTimeStamp = ElapsedTimeToTimeStamp(intervalStartTime);
+ TimeStamp endTimeStamp = ElapsedTimeToTimeStamp(intervalEndTime);
+ TimeStamp iterationTimeStamp = ElapsedTimeToTimeStamp(iterationStartTime);
+
+ AutoTArray<AnimationEventParams, 2> events;
+
+ // Handle cancel event first
+ if ((mPreviousPhase != AnimationPhase::Idle &&
+ mPreviousPhase != AnimationPhase::After) &&
+ currentPhase == AnimationPhase::Idle) {
+ TimeStamp activeTimeStamp = ElapsedTimeToTimeStamp(aActiveTime);
+ events.AppendElement(AnimationEventParams{ eAnimationCancel,
+ aActiveTime,
+ activeTimeStamp });
}
- AutoTArray<EventPair, 2> events;
- StickyTimeDuration initialAdvance = StickyTimeDuration(InitialAdvance());
- StickyTimeDuration iterationStart = computedTiming.mDuration *
- computedTiming.mCurrentIteration;
- const StickyTimeDuration& activeDuration = computedTiming.mActiveDuration;
-
- if (skippedFirstIteration) {
- // Notify animationstart and animationiteration in same tick.
- events.AppendElement(EventPair(eAnimationStart, initialAdvance));
- events.AppendElement(EventPair(eAnimationIteration,
- std::max(iterationStart, initialAdvance)));
- } else if (!wasActive && isActive) {
- events.AppendElement(EventPair(eAnimationStart, initialAdvance));
- } else if (wasActive && !isActive) {
- events.AppendElement(EventPair(eAnimationEnd, activeDuration));
- } else if (wasActive && isActive && !isSameIteration) {
- events.AppendElement(EventPair(eAnimationIteration, iterationStart));
- } else if (skippedActivePhase) {
- events.AppendElement(EventPair(eAnimationStart,
- std::min(initialAdvance, activeDuration)));
- events.AppendElement(EventPair(eAnimationEnd, activeDuration));
- } else {
- return; // No events need to be sent
+ switch (mPreviousPhase) {
+ case AnimationPhase::Idle:
+ case AnimationPhase::Before:
+ if (currentPhase == AnimationPhase::Active) {
+ events.AppendElement(AnimationEventParams{ eAnimationStart,
+ intervalStartTime,
+ startTimeStamp });
+ } else if (currentPhase == AnimationPhase::After) {
+ events.AppendElement(AnimationEventParams{ eAnimationStart,
+ intervalStartTime,
+ startTimeStamp });
+ events.AppendElement(AnimationEventParams{ eAnimationEnd,
+ intervalEndTime,
+ endTimeStamp });
+ }
+ break;
+ case AnimationPhase::Active:
+ if (currentPhase == AnimationPhase::Before) {
+ events.AppendElement(AnimationEventParams{ eAnimationEnd,
+ intervalStartTime,
+ startTimeStamp });
+ } else if (currentPhase == AnimationPhase::Active) {
+ // The currentIteration must have changed or element we would have
+ // returned early above.
+ MOZ_ASSERT(currentIteration != mPreviousIteration);
+ events.AppendElement(AnimationEventParams{ eAnimationIteration,
+ iterationStartTime,
+ iterationTimeStamp });
+ } else if (currentPhase == AnimationPhase::After) {
+ events.AppendElement(AnimationEventParams{ eAnimationEnd,
+ intervalEndTime,
+ endTimeStamp });
+ }
+ break;
+ case AnimationPhase::After:
+ if (currentPhase == AnimationPhase::Before) {
+ events.AppendElement(AnimationEventParams{ eAnimationStart,
+ intervalEndTime,
+ startTimeStamp});
+ events.AppendElement(AnimationEventParams{ eAnimationEnd,
+ intervalStartTime,
+ endTimeStamp });
+ } else if (currentPhase == AnimationPhase::Active) {
+ events.AppendElement(AnimationEventParams{ eAnimationStart,
+ intervalEndTime,
+ endTimeStamp });
+ }
+ break;
}
- for (const EventPair& pair : events){
+ mPreviousPhase = currentPhase;
+ mPreviousIteration = currentIteration;
+
+ for (const AnimationEventParams& event : events){
manager->QueueEvent(
AnimationEventInfo(owningElement, owningPseudoType,
- pair.first(), mAnimationName,
- pair.second(),
- ElapsedTimeToTimeStamp(pair.second()),
+ event.mMessage, mAnimationName,
+ event.mElapsedTime, event.mTimeStamp,
this));
}
}
diff --git a/layout/style/nsAnimationManager.h b/layout/style/nsAnimationManager.h
index abe3aeeb8..d838d090a 100644
--- a/layout/style/nsAnimationManager.h
+++ b/layout/style/nsAnimationManager.h
@@ -76,7 +76,8 @@ public:
, mIsStylePaused(false)
, mPauseShouldStick(false)
, mNeedsNewAnimationIndexWhenRun(false)
- , mPreviousPhaseOrIteration(PREVIOUS_PHASE_BEFORE)
+ , mPreviousPhase(ComputedTiming::AnimationPhase::Idle)
+ , mPreviousIteration(0)
{
// We might need to drop this assertion once we add a script-accessible
// constructor but for animations generated from CSS markup the
@@ -109,8 +110,6 @@ public:
void PauseFromStyle();
void CancelFromStyle() override
{
- mOwningElement = OwningElementRef();
-
// When an animation is disassociated with style it enters an odd state
// where its composite order is undefined until it first transitions
// out of the idle state.
@@ -125,10 +124,15 @@ public:
mNeedsNewAnimationIndexWhenRun = true;
Animation::CancelFromStyle();
+
+ // We need to do this *after* calling CancelFromStyle() since
+ // CancelFromStyle might synchronously trigger a cancel event for which
+ // we need an owning element to target the event at.
+ mOwningElement = OwningElementRef();
}
void Tick() override;
- void QueueEvents();
+ void QueueEvents(StickyTimeDuration aActiveTime = StickyTimeDuration());
bool IsStylePaused() const { return mIsStylePaused; }
@@ -157,6 +161,10 @@ public:
// reflect changes to that markup.
bool IsTiedToMarkup() const { return mOwningElement.IsSet(); }
+ void MaybeQueueCancelEvent(StickyTimeDuration aActiveTime) override {
+ QueueEvents(aActiveTime);
+ }
+
protected:
virtual ~CSSAnimation()
{
@@ -257,13 +265,10 @@ protected:
// its animation index should be updated.
bool mNeedsNewAnimationIndexWhenRun;
- enum {
- PREVIOUS_PHASE_BEFORE = uint64_t(-1),
- PREVIOUS_PHASE_AFTER = uint64_t(-2)
- };
- // One of the PREVIOUS_PHASE_* constants, or an integer for the iteration
- // whose start we last notified on.
- uint64_t mPreviousPhaseOrIteration;
+ // Phase and current iteration from the previous time we queued events.
+ // This is used to determine what new events to dispatch.
+ ComputedTiming::AnimationPhase mPreviousPhase;
+ uint64_t mPreviousIteration;
};
} /* namespace dom */
diff --git a/layout/style/nsCSSKeywordList.h b/layout/style/nsCSSKeywordList.h
index 933ff6e7b..94968faca 100644
--- a/layout/style/nsCSSKeywordList.h
+++ b/layout/style/nsCSSKeywordList.h
@@ -238,6 +238,7 @@ CSS_KEY(disc, disc)
CSS_KEY(disclosure-closed, disclosure_closed)
CSS_KEY(disclosure-open, disclosure_open)
CSS_KEY(discretionary-ligatures, discretionary_ligatures)
+CSS_KEY(distribute, distribute)
CSS_KEY(dot, dot)
CSS_KEY(dotted, dotted)
CSS_KEY(double, double)
@@ -333,7 +334,8 @@ CSS_KEY(inline-start, inline_start)
CSS_KEY(inline-table, inline_table)
CSS_KEY(inset, inset)
CSS_KEY(inside, inside)
-// CSS_KEY(inter-character, inter_character) // TODO see bug 1055672
+CSS_KEY(inter-character, inter_character)
+CSS_KEY(inter-word, inter_word)
CSS_KEY(interpolatematrix, interpolatematrix)
CSS_KEY(intersect, intersect)
CSS_KEY(isolate, isolate)
diff --git a/layout/style/nsCSSPropList.h b/layout/style/nsCSSPropList.h
index 6931d8c2b..b04921dcb 100644
--- a/layout/style/nsCSSPropList.h
+++ b/layout/style/nsCSSPropList.h
@@ -4027,6 +4027,17 @@ CSS_PROP_TEXT(
nullptr,
offsetof(nsStyleText, mTextIndent),
eStyleAnimType_Coord)
+CSS_PROP_TEXT(
+ text-justify,
+ text_justify,
+ TextJustify,
+ CSS_PROPERTY_PARSE_VALUE |
+ CSS_PROPERTY_APPLIES_TO_PLACEHOLDER,
+ "layout.css.text-justify.enabled",
+ VARIANT_HK,
+ kTextJustifyKTable,
+ CSS_PROP_NO_OFFSET,
+ eStyleAnimType_Discrete)
CSS_PROP_VISIBILITY(
text-orientation,
text_orientation,
diff --git a/layout/style/nsCSSProps.cpp b/layout/style/nsCSSProps.cpp
index f3a7f898d..9805eae14 100644
--- a/layout/style/nsCSSProps.cpp
+++ b/layout/style/nsCSSProps.cpp
@@ -2035,6 +2035,17 @@ KTableEntry nsCSSProps::kTextAlignLastKTable[] = {
{ eCSSKeyword_UNKNOWN, -1 }
};
+const KTableEntry nsCSSProps::kTextJustifyKTable[] = {
+ { eCSSKeyword_none, StyleTextJustify::None },
+ { eCSSKeyword_auto, StyleTextJustify::Auto },
+ { eCSSKeyword_inter_word, StyleTextJustify::InterWord },
+ { eCSSKeyword_inter_character, StyleTextJustify::InterCharacter },
+ // For legacy reasons, UAs must also support the keyword "distribute" with
+ // the exact same meaning and behavior as "inter-character".
+ { eCSSKeyword_distribute, StyleTextJustify::InterCharacter },
+ { eCSSKeyword_UNKNOWN, -1 }
+};
+
const KTableEntry nsCSSProps::kTextCombineUprightKTable[] = {
{ eCSSKeyword_none, NS_STYLE_TEXT_COMBINE_UPRIGHT_NONE },
{ eCSSKeyword_all, NS_STYLE_TEXT_COMBINE_UPRIGHT_ALL },
diff --git a/layout/style/nsCSSProps.h b/layout/style/nsCSSProps.h
index ab78e6174..dfe35afd8 100644
--- a/layout/style/nsCSSProps.h
+++ b/layout/style/nsCSSProps.h
@@ -869,6 +869,7 @@ public:
static const KTableEntry kTextEmphasisPositionKTable[];
static const KTableEntry kTextEmphasisStyleFillKTable[];
static const KTableEntry kTextEmphasisStyleShapeKTable[];
+ static const KTableEntry kTextJustifyKTable[];
static const KTableEntry kTextOrientationKTable[];
static const KTableEntry kTextOverflowKTable[];
static const KTableEntry kTextTransformKTable[];
diff --git a/layout/style/nsComputedDOMStyle.cpp b/layout/style/nsComputedDOMStyle.cpp
index 4eb24b76b..4f8d3edf6 100644
--- a/layout/style/nsComputedDOMStyle.cpp
+++ b/layout/style/nsComputedDOMStyle.cpp
@@ -3875,6 +3875,16 @@ nsComputedDOMStyle::DoGetTextIndent()
}
already_AddRefed<CSSValue>
+nsComputedDOMStyle::DoGetTextJustify()
+{
+ RefPtr<nsROCSSPrimitiveValue> val = new nsROCSSPrimitiveValue;
+ val->SetIdent(
+ nsCSSProps::ValueToKeywordEnum(StyleText()->mTextJustify,
+ nsCSSProps::kTextJustifyKTable));
+ return val.forget();
+}
+
+already_AddRefed<CSSValue>
nsComputedDOMStyle::DoGetTextOrientation()
{
RefPtr<nsROCSSPrimitiveValue> val = new nsROCSSPrimitiveValue;
diff --git a/layout/style/nsComputedDOMStyle.h b/layout/style/nsComputedDOMStyle.h
index 223b29a14..27e2086e9 100644
--- a/layout/style/nsComputedDOMStyle.h
+++ b/layout/style/nsComputedDOMStyle.h
@@ -421,6 +421,7 @@ private:
already_AddRefed<CSSValue> DoGetTextEmphasisPosition();
already_AddRefed<CSSValue> DoGetTextEmphasisStyle();
already_AddRefed<CSSValue> DoGetTextIndent();
+ already_AddRefed<CSSValue> DoGetTextJustify();
already_AddRefed<CSSValue> DoGetTextOrientation();
already_AddRefed<CSSValue> DoGetTextOverflow();
already_AddRefed<CSSValue> DoGetTextTransform();
diff --git a/layout/style/nsComputedDOMStylePropertyList.h b/layout/style/nsComputedDOMStylePropertyList.h
index 7c0457e34..1983208ac 100644
--- a/layout/style/nsComputedDOMStylePropertyList.h
+++ b/layout/style/nsComputedDOMStylePropertyList.h
@@ -239,6 +239,7 @@ COMPUTED_STYLE_PROP(text_emphasis_color, TextEmphasisColor)
COMPUTED_STYLE_PROP(text_emphasis_position, TextEmphasisPosition)
COMPUTED_STYLE_PROP(text_emphasis_style, TextEmphasisStyle)
COMPUTED_STYLE_PROP(text_indent, TextIndent)
+COMPUTED_STYLE_PROP(text_justify, TextJustify)
COMPUTED_STYLE_PROP(text_orientation, TextOrientation)
COMPUTED_STYLE_PROP(text_overflow, TextOverflow)
COMPUTED_STYLE_PROP(text_shadow, TextShadow)
diff --git a/layout/style/nsRuleNode.cpp b/layout/style/nsRuleNode.cpp
index fa29fe0f1..9b9fc3948 100644
--- a/layout/style/nsRuleNode.cpp
+++ b/layout/style/nsRuleNode.cpp
@@ -1414,6 +1414,7 @@ struct SetEnumValueHelper
DEFINE_ENUM_CLASS_SETTER(StyleFillRule, Nonzero, Evenodd)
DEFINE_ENUM_CLASS_SETTER(StyleFloat, None, InlineEnd)
DEFINE_ENUM_CLASS_SETTER(StyleFloatEdge, ContentBox, MarginBox)
+ DEFINE_ENUM_CLASS_SETTER(StyleTextJustify, None, InterCharacter)
DEFINE_ENUM_CLASS_SETTER(StyleUserFocus, None, SelectMenu)
DEFINE_ENUM_CLASS_SETTER(StyleUserSelect, None, MozText)
DEFINE_ENUM_CLASS_SETTER(StyleUserInput, None, Auto)
@@ -4783,6 +4784,12 @@ nsRuleNode::ComputeTextData(void* aStartStruct,
SETCOORD_UNSET_INHERIT,
aContext, mPresContext, conditions);
+ // text-justify: enum, inherit, initial
+ SetValue(*aRuleData->ValueForTextJustify(), text->mTextJustify, conditions,
+ SETVAL_ENUMERATED | SETVAL_UNSET_INHERIT,
+ parentText->mTextJustify,
+ StyleTextJustify::Auto);
+
// text-transform: enum, inherit, initial
SetValue(*aRuleData->ValueForTextTransform(), text->mTextTransform, conditions,
SETVAL_ENUMERATED | SETVAL_UNSET_INHERIT,
diff --git a/layout/style/nsStyleConsts.h b/layout/style/nsStyleConsts.h
index ee78dcb64..be588113e 100644
--- a/layout/style/nsStyleConsts.h
+++ b/layout/style/nsStyleConsts.h
@@ -185,6 +185,14 @@ enum class StyleShapeSourceType : uint8_t {
Box,
};
+// text-justify
+enum class StyleTextJustify : uint8_t {
+ None,
+ Auto,
+ InterWord,
+ InterCharacter,
+};
+
// user-focus
enum class StyleUserFocus : uint8_t {
None,
diff --git a/layout/style/nsStyleStruct.cpp b/layout/style/nsStyleStruct.cpp
index 553239e0e..52491a288 100644
--- a/layout/style/nsStyleStruct.cpp
+++ b/layout/style/nsStyleStruct.cpp
@@ -3788,6 +3788,7 @@ nsStyleText::nsStyleText(StyleStructContext aContext)
, mTextAlignLast(NS_STYLE_TEXT_ALIGN_AUTO)
, mTextAlignTrue(false)
, mTextAlignLastTrue(false)
+ , mTextJustify(StyleTextJustify::Auto)
, mTextTransform(NS_STYLE_TEXT_TRANSFORM_NONE)
, mWhiteSpace(NS_STYLE_WHITESPACE_NORMAL)
, mWordBreak(NS_STYLE_WORDBREAK_NORMAL)
@@ -3824,6 +3825,7 @@ nsStyleText::nsStyleText(const nsStyleText& aSource)
, mTextAlignLast(aSource.mTextAlignLast)
, mTextAlignTrue(false)
, mTextAlignLastTrue(false)
+ , mTextJustify(aSource.mTextJustify)
, mTextTransform(aSource.mTextTransform)
, mWhiteSpace(aSource.mWhiteSpace)
, mWordBreak(aSource.mWordBreak)
@@ -3885,6 +3887,7 @@ nsStyleText::CalcDifference(const nsStyleText& aNewData) const
(mTextSizeAdjust != aNewData.mTextSizeAdjust) ||
(mLetterSpacing != aNewData.mLetterSpacing) ||
(mLineHeight != aNewData.mLineHeight) ||
+ (mTextJustify != aNewData.mTextJustify) ||
(mTextIndent != aNewData.mTextIndent) ||
(mWordSpacing != aNewData.mWordSpacing) ||
(mTabSize != aNewData.mTabSize)) {
diff --git a/layout/style/nsStyleStruct.h b/layout/style/nsStyleStruct.h
index 05a6be91e..1cadea840 100644
--- a/layout/style/nsStyleStruct.h
+++ b/layout/style/nsStyleStruct.h
@@ -2071,6 +2071,7 @@ struct MOZ_NEEDS_MEMMOVABLE_MEMBERS nsStyleText
uint8_t mTextAlignLast; // [inherited] see nsStyleConsts.h
bool mTextAlignTrue : 1; // [inherited] see nsStyleConsts.h
bool mTextAlignLastTrue : 1; // [inherited] see nsStyleConsts.h
+ mozilla::StyleTextJustify mTextJustify; // [inherited]
uint8_t mTextTransform; // [inherited] see nsStyleConsts.h
uint8_t mWhiteSpace; // [inherited] see nsStyleConsts.h
uint8_t mWordBreak; // [inherited] see nsStyleConsts.h
diff --git a/layout/style/nsTransitionManager.cpp b/layout/style/nsTransitionManager.cpp
index 4a1a5b7ad..118702e8f 100644
--- a/layout/style/nsTransitionManager.cpp
+++ b/layout/style/nsTransitionManager.cpp
@@ -46,8 +46,6 @@ using mozilla::dom::KeyframeEffectReadOnly;
using namespace mozilla;
using namespace mozilla::css;
-typedef mozilla::ComputedTiming::AnimationPhase AnimationPhase;
-
namespace {
struct TransitionEventParams {
EventMessage mMessage;
@@ -180,10 +178,9 @@ CSSTransition::UpdateTiming(SeekFlag aSeekFlag, SyncNotifyFlag aSyncNotifyFlag)
}
void
-CSSTransition::QueueEvents()
+CSSTransition::QueueEvents(StickyTimeDuration aActiveTime)
{
- if (!mEffect ||
- !mOwningElement.IsSet()) {
+ if (!mOwningElement.IsSet()) {
return;
}
@@ -197,69 +194,123 @@ CSSTransition::QueueEvents()
return;
}
- ComputedTiming computedTiming = mEffect->GetComputedTiming();
- const StickyTimeDuration zeroDuration;
- StickyTimeDuration intervalStartTime =
- std::max(std::min(StickyTimeDuration(-mEffect->SpecifiedTiming().mDelay),
- computedTiming.mActiveDuration), zeroDuration);
- StickyTimeDuration intervalEndTime =
- std::max(std::min((EffectEnd() - mEffect->SpecifiedTiming().mDelay),
- computedTiming.mActiveDuration), zeroDuration);
+ const StickyTimeDuration zeroDuration = StickyTimeDuration();
+
+ TransitionPhase currentPhase;
+ StickyTimeDuration intervalStartTime;
+ StickyTimeDuration intervalEndTime;
+
+ if (!mEffect) {
+ currentPhase = GetAnimationPhaseWithoutEffect<TransitionPhase>(*this);
+ intervalStartTime = zeroDuration;
+ intervalEndTime = zeroDuration;
+ } else {
+ ComputedTiming computedTiming = mEffect->GetComputedTiming();
+
+ currentPhase = static_cast<TransitionPhase>(computedTiming.mPhase);
+ intervalStartTime =
+ std::max(std::min(StickyTimeDuration(-mEffect->SpecifiedTiming().mDelay),
+ computedTiming.mActiveDuration), zeroDuration);
+ intervalEndTime =
+ std::max(std::min((EffectEnd() - mEffect->SpecifiedTiming().mDelay),
+ computedTiming.mActiveDuration), zeroDuration);
+ }
// TimeStamps to use for ordering the events when they are dispatched. We
// use a TimeStamp so we can compare events produced by different elements,
// perhaps even with different timelines.
// The zero timestamp is for transitionrun events where we ignore the delay
// for the purpose of ordering events.
- TimeStamp startTimeStamp = ElapsedTimeToTimeStamp(intervalStartTime);
- TimeStamp endTimeStamp = ElapsedTimeToTimeStamp(intervalEndTime);
+ TimeStamp zeroTimeStamp = AnimationTimeToTimeStamp(zeroDuration);
+ TimeStamp startTimeStamp = ElapsedTimeToTimeStamp(intervalStartTime);
+ TimeStamp endTimeStamp = ElapsedTimeToTimeStamp(intervalEndTime);
- TransitionPhase currentPhase;
if (mPendingState != PendingState::NotPending &&
(mPreviousTransitionPhase == TransitionPhase::Idle ||
mPreviousTransitionPhase == TransitionPhase::Pending))
{
currentPhase = TransitionPhase::Pending;
- } else {
- currentPhase = static_cast<TransitionPhase>(computedTiming.mPhase);
}
AutoTArray<TransitionEventParams, 3> events;
+
+ // Handle cancel events firts
+ if (mPreviousTransitionPhase != TransitionPhase::Idle &&
+ currentPhase == TransitionPhase::Idle) {
+ TimeStamp activeTimeStamp = ElapsedTimeToTimeStamp(aActiveTime);
+ events.AppendElement(TransitionEventParams{ eTransitionCancel,
+ aActiveTime,
+ activeTimeStamp });
+ }
+
+ // All other events
switch (mPreviousTransitionPhase) {
case TransitionPhase::Idle:
- if (currentPhase == TransitionPhase::After) {
+ if (currentPhase == TransitionPhase::Pending ||
+ currentPhase == TransitionPhase::Before) {
+ events.AppendElement(TransitionEventParams{ eTransitionRun,
+ intervalStartTime,
+ zeroTimeStamp });
+ } else if (currentPhase == TransitionPhase::Active) {
+ events.AppendElement(TransitionEventParams{ eTransitionRun,
+ intervalStartTime,
+ zeroTimeStamp });
+ events.AppendElement(TransitionEventParams{ eTransitionStart,
+ intervalStartTime,
+ startTimeStamp });
+ } else if (currentPhase == TransitionPhase::After) {
+ events.AppendElement(TransitionEventParams{ eTransitionRun,
+ intervalStartTime,
+ zeroTimeStamp });
+ events.AppendElement(TransitionEventParams{ eTransitionStart,
+ intervalStartTime,
+ startTimeStamp });
events.AppendElement(TransitionEventParams{ eTransitionEnd,
- intervalEndTime,
- endTimeStamp });
+ intervalEndTime,
+ endTimeStamp });
}
break;
case TransitionPhase::Pending:
case TransitionPhase::Before:
- if (currentPhase == TransitionPhase::After) {
+ if (currentPhase == TransitionPhase::Active) {
+ events.AppendElement(TransitionEventParams{ eTransitionStart,
+ intervalStartTime,
+ startTimeStamp });
+ } else if (currentPhase == TransitionPhase::After) {
+ events.AppendElement(TransitionEventParams{ eTransitionStart,
+ intervalStartTime,
+ startTimeStamp });
events.AppendElement(TransitionEventParams{ eTransitionEnd,
- intervalEndTime,
- endTimeStamp });
+ intervalEndTime,
+ endTimeStamp });
}
break;
case TransitionPhase::Active:
if (currentPhase == TransitionPhase::After) {
events.AppendElement(TransitionEventParams{ eTransitionEnd,
- intervalEndTime,
- endTimeStamp });
+ intervalEndTime,
+ endTimeStamp });
} else if (currentPhase == TransitionPhase::Before) {
events.AppendElement(TransitionEventParams{ eTransitionEnd,
- intervalStartTime,
- startTimeStamp });
+ intervalStartTime,
+ startTimeStamp });
}
break;
case TransitionPhase::After:
- if (currentPhase == TransitionPhase::Before) {
+ if (currentPhase == TransitionPhase::Active) {
+ events.AppendElement(TransitionEventParams{ eTransitionStart,
+ intervalEndTime,
+ startTimeStamp });
+ } else if (currentPhase == TransitionPhase::Before) {
+ events.AppendElement(TransitionEventParams{ eTransitionStart,
+ intervalEndTime,
+ startTimeStamp });
events.AppendElement(TransitionEventParams{ eTransitionEnd,
- intervalStartTime,
- endTimeStamp });
+ intervalStartTime,
+ endTimeStamp });
}
break;
}
diff --git a/layout/style/nsTransitionManager.h b/layout/style/nsTransitionManager.h
index 56ec61572..1c48cc8cd 100644
--- a/layout/style/nsTransitionManager.h
+++ b/layout/style/nsTransitionManager.h
@@ -214,6 +214,10 @@ public:
const TimeDuration& aStartTime,
double aPlaybackRate);
+ void MaybeQueueCancelEvent(StickyTimeDuration aActiveTime) override {
+ QueueEvents(aActiveTime);
+ }
+
protected:
virtual ~CSSTransition()
{
@@ -225,7 +229,10 @@ protected:
void UpdateTiming(SeekFlag aSeekFlag,
SyncNotifyFlag aSyncNotifyFlag) override;
- void QueueEvents();
+ void QueueEvents(StickyTimeDuration activeTime = StickyTimeDuration());
+
+
+ enum class TransitionPhase;
// The (pseudo-)element whose computed transition-property refers to this
// transition (if any).
@@ -250,7 +257,7 @@ protected:
// to be queued on this tick.
// See: https://drafts.csswg.org/css-transitions-2/#transition-phase
enum class TransitionPhase {
- Idle = static_cast<int>(ComputedTiming::AnimationPhase::Null),
+ Idle = static_cast<int>(ComputedTiming::AnimationPhase::Idle),
Before = static_cast<int>(ComputedTiming::AnimationPhase::Before),
Active = static_cast<int>(ComputedTiming::AnimationPhase::Active),
After = static_cast<int>(ComputedTiming::AnimationPhase::After),
diff --git a/layout/style/test/property_database.js b/layout/style/test/property_database.js
index 62d413d98..272931c15 100644
--- a/layout/style/test/property_database.js
+++ b/layout/style/test/property_database.js
@@ -5694,6 +5694,17 @@ if (IsCSSPropertyPrefEnabled("layout.css.text-combine-upright.enabled")) {
}
}
+if (IsCSSPropertyPrefEnabled("layout.css.text-justify.enabled")) {
+ gCSSProperties["text-justify"] = {
+ domProp: "textJustify",
+ inherited: true,
+ type: CSS_TYPE_LONGHAND,
+ initial_values: [ "auto" ],
+ other_values: [ "none", "inter-word", "inter-character", "distribute" ],
+ invalid_values: []
+ };
+}
+
if (IsCSSPropertyPrefEnabled("svg.paint-order.enabled")) {
gCSSProperties["paint-order"] = {
domProp: "paintOrder",
diff --git a/layout/style/test/test_animations.html b/layout/style/test/test_animations.html
index eaccba122..4019af77f 100644
--- a/layout/style/test/test_animations.html
+++ b/layout/style/test/test_animations.html
@@ -1195,9 +1195,6 @@ is_approx(px_to_num(cs.marginRight), 100 * gTF.ease_in(0.4), 0.01,
"large negative delay test at 0ms");
check_events([{ type: 'animationstart', target: div,
animationName: 'anim2', elapsedTime: 3.6,
- pseudoElement: "" },
- { type: 'animationiteration', target: div,
- animationName: 'anim2', elapsedTime: 3.6,
pseudoElement: "" }],
"right after start in large negative delay test");
advance_clock(380);
diff --git a/layout/style/test/test_animations_event_handler_attribute.html b/layout/style/test/test_animations_event_handler_attribute.html
index e5def2b34..036a77779 100644
--- a/layout/style/test/test_animations_event_handler_attribute.html
+++ b/layout/style/test/test_animations_event_handler_attribute.html
@@ -88,21 +88,55 @@ checkReceivedEvents("animationend", targets);
targets.forEach(div => { div.remove(); });
-// 2. Test CSS Transition event handlers.
+// 2a. Test CSS Transition event handlers (without transitioncancel)
-var targets = createAndRegisterTargets([ 'ontransitionend' ]);
+var targets = createAndRegisterTargets([ 'ontransitionrun',
+ 'ontransitionstart',
+ 'ontransitionend',
+ 'ontransitioncancel' ]);
targets.forEach(div => {
- div.style.transition = 'margin-left 100ms';
+ div.style.transition = 'margin-left 100ms 200ms';
getComputedStyle(div).marginLeft; // flush
div.style.marginLeft = "200px";
getComputedStyle(div).marginLeft; // flush
});
+advance_clock(0);
+checkReceivedEvents("transitionrun", targets);
+
+advance_clock(200);
+checkReceivedEvents("transitionstart", targets);
+
advance_clock(100);
checkReceivedEvents("transitionend", targets);
targets.forEach(div => { div.remove(); });
+// 2b. Test CSS Transition cancel event handler.
+
+var targets = createAndRegisterTargets([ 'ontransitioncancel' ]);
+targets.forEach(div => {
+ div.style.transition = 'margin-left 100ms 200ms';
+ getComputedStyle(div).marginLeft; // flush
+ div.style.marginLeft = "200px";
+ getComputedStyle(div).marginLeft; // flush
+});
+
+advance_clock(200);
+
+targets.forEach(div => {
+ div.style.display = "none"
+});
+getComputedStyle(targets[0]).display; // flush
+
+advance_clock(0);
+checkReceivedEvents("transitioncancel", targets);
+
+advance_clock(100);
+targets.forEach( div => { is(div.receivedEventType, undefined); });
+
+targets.forEach(div => { div.remove(); });
+
// 3. Test prefixed CSS Animation event handlers.
var targets = createAndRegisterTargets([ 'onwebkitanimationstart',
diff --git a/layout/style/test/test_animations_event_order.html b/layout/style/test/test_animations_event_order.html
index 5af7639cc..7204934d2 100644
--- a/layout/style/test/test_animations_event_order.html
+++ b/layout/style/test/test_animations_event_order.html
@@ -46,7 +46,10 @@ var gDisplay = document.getElementById('display');
[ 'animationstart',
'animationiteration',
'animationend',
- 'transitionend' ]
+ 'transitionrun',
+ 'transitionstart',
+ 'transitionend',
+ 'transitioncancel' ]
.forEach(event =>
gDisplay.addEventListener(event,
event => gEventsReceived.push(event),
@@ -322,9 +325,13 @@ getComputedStyle(divs[0]).marginLeft;
advance_clock(0);
advance_clock(10000);
-checkEventOrder([ divs[0], 'transitionend' ],
+checkEventOrder([ divs[0], 'transitionrun' ],
+ [ divs[0], 'transitionstart' ],
+ [ divs[1], 'transitionrun' ],
+ [ divs[1], 'transitionstart' ],
+ [ divs[0], 'transitionend' ],
[ divs[1], 'transitionend' ],
- 'Simultaneous transitionend on siblings');
+ 'Simultaneous transitionrun/start/end on siblings');
divs.forEach(div => div.remove());
divs = [];
@@ -360,10 +367,16 @@ getComputedStyle(divs[0]).marginLeft;
advance_clock(0);
advance_clock(10000);
-checkEventOrder([ divs[0], 'transitionend' ],
+checkEventOrder([ divs[0], 'transitionrun' ],
+ [ divs[0], 'transitionstart' ],
+ [ divs[2], 'transitionrun' ],
+ [ divs[2], 'transitionstart' ],
+ [ divs[1], 'transitionrun' ],
+ [ divs[1], 'transitionstart' ],
+ [ divs[0], 'transitionend' ],
[ divs[2], 'transitionend' ],
[ divs[1], 'transitionend' ],
- 'Simultaneous transitionend on children');
+ 'Simultaneous transitionrun/start/end on children');
divs.forEach(div => div.remove());
divs = [];
@@ -408,11 +421,19 @@ getComputedStyle(divs[0]).marginLeft;
advance_clock(0);
advance_clock(10000);
-checkEventOrder([ divs[0], 'transitionend' ],
+checkEventOrder([ divs[0], 'transitionrun' ],
+ [ divs[0], 'transitionstart' ],
+ [ divs[0], '::before', 'transitionrun' ],
+ [ divs[0], '::before', 'transitionstart' ],
+ [ divs[0], '::after', 'transitionrun' ],
+ [ divs[0], '::after', 'transitionstart' ],
+ [ divs[1], 'transitionrun' ],
+ [ divs[1], 'transitionstart' ],
+ [ divs[0], 'transitionend' ],
[ divs[0], '::before', 'transitionend' ],
[ divs[0], '::after', 'transitionend' ],
[ divs[1], 'transitionend' ],
- 'Simultaneous transitionend on pseudo-elements');
+ 'Simultaneous transitionrun/start/end on pseudo-elements');
divs.forEach(div => div.remove());
divs = [];
@@ -441,9 +462,13 @@ getComputedStyle(divs[0]).marginLeft;
advance_clock(0);
advance_clock(10000);
-checkEventOrder([ divs[1], 'transitionend' ],
+checkEventOrder([ divs[0], 'transitionrun' ],
+ [ divs[0], 'transitionstart' ],
+ [ divs[1], 'transitionrun' ],
+ [ divs[1], 'transitionstart' ],
+ [ divs[1], 'transitionend' ],
[ divs[0], 'transitionend' ],
- 'Sorting of transitionend events by time');
+ 'Sorting of transitionrun/start/end events by time');
divs.forEach(div => div.remove());
divs = [];
@@ -468,9 +493,13 @@ getComputedStyle(divs[0]).marginLeft;
advance_clock(0);
advance_clock(10 * 1000);
-checkEventOrder([ divs[1], 'transitionend' ],
+checkEventOrder([ divs[0], 'transitionrun' ],
+ [ divs[1], 'transitionrun' ],
+ [ divs[1], 'transitionstart' ],
+ [ divs[0], 'transitionstart' ],
+ [ divs[1], 'transitionend' ],
[ divs[0], 'transitionend' ],
- 'Sorting of transitionend events by time' +
+ 'Sorting of transitionrun/start/end events by time' +
'(including delay)');
divs.forEach(div => div.remove());
@@ -492,9 +521,14 @@ getComputedStyle(div).marginLeft;
advance_clock(0);
advance_clock(10000);
-checkEventOrder([ 'margin-left', 'transitionend' ],
+checkEventOrder([ 'margin-left', 'transitionrun' ],
+ [ 'margin-left', 'transitionstart' ],
+ [ 'opacity', 'transitionrun' ],
+ [ 'opacity', 'transitionstart' ],
+ [ 'margin-left', 'transitionend' ],
[ 'opacity', 'transitionend' ],
- 'Sorting of transitionend events by transition-property')
+ 'Sorting of transitionrun/start/end events by ' +
+ 'transition-property')
div.remove();
div = undefined;
@@ -519,7 +553,11 @@ getComputedStyle(divs[0]).marginLeft;
advance_clock(0);
advance_clock(10000);
-checkEventOrder([ divs[0], 'transitionend' ],
+checkEventOrder([ divs[0], 'transitionrun' ],
+ [ divs[0], 'transitionstart' ],
+ [ divs[1], 'transitionrun' ],
+ [ divs[1], 'transitionstart' ],
+ [ divs[0], 'transitionend' ],
[ divs[1], 'transitionend' ],
'Transition events are sorted by document position first, ' +
'before transition-property');
@@ -543,7 +581,11 @@ getComputedStyle(div).marginLeft;
advance_clock(0);
advance_clock(10000);
-checkEventOrder([ 'opacity', 'transitionend' ],
+checkEventOrder([ 'margin-left', 'transitionrun' ],
+ [ 'margin-left', 'transitionstart' ],
+ [ 'opacity', 'transitionrun' ],
+ [ 'opacity', 'transitionstart' ],
+ [ 'opacity', 'transitionend' ],
[ 'margin-left', 'transitionend' ],
'Transition events are sorted by time first, before ' +
'transition-property');
@@ -571,9 +613,50 @@ getComputedStyle(divs[0]).marginLeft;
advance_clock(0);
advance_clock(15 * 1000);
-checkEventOrder([ divs[1], 'transitionend' ],
+checkEventOrder([ divs[0], 'transitionrun' ],
+ [ divs[1], 'transitionrun' ],
+ [ divs[1], 'transitionstart' ],
+ [ divs[0], 'transitionstart' ],
+ [ divs[1], 'transitionend' ],
[ divs[0], 'transitionend' ],
- 'Simultaneous transitionend on siblings');
+ 'Simultaneous transitionrun/start/end on siblings');
+
+divs.forEach(div => div.remove());
+divs = [];
+
+// 4j. Test sorting transitions with cancel
+// The order of transitioncancel is based on StyleManager.
+// So this test looks like wrong result at a glance. However
+// the gecko will cancel div1's transition before div2 in this case.
+
+divs = [ document.createElement('div'),
+ document.createElement('div') ];
+divs.forEach((div, i) => {
+ gDisplay.appendChild(div);
+ div.style.marginLeft = '0px';
+ div.setAttribute('id', 'div' + i);
+});
+
+divs[0].style.transition = 'margin-left 10s 5s';
+divs[1].style.transition = 'margin-left 10s';
+
+getComputedStyle(divs[0]).marginLeft;
+divs.forEach(div => div.style.marginLeft = '100px');
+getComputedStyle(divs[0]).marginLeft;
+
+advance_clock(0);
+advance_clock(5 * 1000);
+divs.forEach(div => div.style.display = 'none' );
+getComputedStyle(divs[0]).display;
+advance_clock(10 * 1000);
+
+checkEventOrder([ divs[0], 'transitionrun' ],
+ [ divs[1], 'transitionrun' ],
+ [ divs[1], 'transitionstart' ],
+ [ divs[0], 'transitionstart' ],
+ [ divs[1], 'transitioncancel' ],
+ [ divs[0], 'transitioncancel' ],
+ 'Simultaneous transitionrun/start/cancel on siblings');
divs.forEach(div => div.remove());
divs = [];
diff --git a/layout/style/test/test_animations_omta.html b/layout/style/test/test_animations_omta.html
index 4b276c896..0b2a61ecc 100644
--- a/layout/style/test/test_animations_omta.html
+++ b/layout/style/test/test_animations_omta.html
@@ -1408,9 +1408,6 @@ addAsyncAnimTest(function *() {
"large negative delay test at 0ms");
check_events([{ type: 'animationstart', target: gDiv,
animationName: 'anim2', elapsedTime: 3.6,
- pseudoElement: "" },
- { type: 'animationiteration', target: gDiv,
- animationName: 'anim2', elapsedTime: 3.6,
pseudoElement: "" }],
"right after start in large negative delay test");
advance_clock(380);