diff options
Diffstat (limited to 'layout')
34 files changed, 674 insertions, 153 deletions
diff --git a/layout/base/nsLayoutUtils.cpp b/layout/base/nsLayoutUtils.cpp index c8c91b251..86874f404 100644 --- a/layout/base/nsLayoutUtils.cpp +++ b/layout/base/nsLayoutUtils.cpp @@ -7013,7 +7013,8 @@ nsLayoutUtils::GetTextRunFlagsForStyle(nsStyleContext* aStyleContext, nscoord aLetterSpacing) { uint32_t result = 0; - if (aLetterSpacing != 0) { + if (aLetterSpacing != 0 || + aStyleText->mTextJustify == StyleTextJustify::InterCharacter) { result |= gfxTextRunFactory::TEXT_DISABLE_OPTIONAL_LIGATURES; } if (aStyleText->mControlCharacterVisibility == NS_STYLE_CONTROL_CHARACTER_VISIBILITY_HIDDEN) { diff --git a/layout/generic/nsTextFrame.cpp b/layout/generic/nsTextFrame.cpp index 00c0016fd..fa31443fd 100644 --- a/layout/generic/nsTextFrame.cpp +++ b/layout/generic/nsTextFrame.cpp @@ -2936,22 +2936,40 @@ nsTextFrame::GetTrimmedOffsets(const nsTextFragment* aFrag, return offsets; } -static bool IsJustifiableCharacter(const nsTextFragment* aFrag, int32_t aPos, +static bool IsJustifiableCharacter(const nsStyleText* aTextStyle, + const nsTextFragment* aFrag, int32_t aPos, bool aLangIsCJ) { NS_ASSERTION(aPos >= 0, "negative position?!"); + + StyleTextJustify justifyStyle = aTextStyle->mTextJustify; + if (justifyStyle == StyleTextJustify::None) { + return false; + } + char16_t ch = aFrag->CharAt(aPos); - if (ch == '\n' || ch == '\t' || ch == '\r') + if (ch == '\n' || ch == '\t' || ch == '\r') { return true; + } if (ch == ' ' || ch == CH_NBSP) { // Don't justify spaces that are combined with diacriticals - if (!aFrag->Is2b()) + if (!aFrag->Is2b()) { return true; + } return !nsTextFrameUtils::IsSpaceCombiningSequenceTail( - aFrag->Get2b() + aPos + 1, aFrag->GetLength() - (aPos + 1)); + aFrag->Get2b() + aPos + 1, aFrag->GetLength() - (aPos + 1)); } - if (ch < 0x2150u) + + if (justifyStyle == StyleTextJustify::InterCharacter) { + return true; + } else if (justifyStyle == StyleTextJustify::InterWord) { + return false; + } + + // text-justify: auto + if (ch < 0x2150u) { return false; + } if (aLangIsCJ) { if ((0x2150u <= ch && ch <= 0x22ffu) || // Number Forms, Arrows, Mathematical Operators (0x2460u <= ch && ch <= 0x24ffu) || // Enclosed Alphanumerics @@ -3279,7 +3297,7 @@ PropertyProvider::ComputeJustification( gfxSkipCharsIterator iter = run.GetPos(); for (uint32_t i = 0; i < length; ++i) { uint32_t offset = originalOffset + i; - if (!IsJustifiableCharacter(mFrag, offset, isCJ)) { + if (!IsJustifiableCharacter(mTextStyle, mFrag, offset, isCJ)) { continue; } diff --git a/layout/reftests/transform-3d/animate-backface-hidden.html b/layout/reftests/transform-3d/animate-backface-hidden.html index ce957bf73..27b587006 100644 --- a/layout/reftests/transform-3d/animate-backface-hidden.html +++ b/layout/reftests/transform-3d/animate-backface-hidden.html @@ -17,7 +17,7 @@ body { padding: 50px } height: 200px; width: 200px; backface-visibility: hidden; /* use a -99.9s delay to start at 99.9% and then move to 0% */ - animation: flip 100s -99.9s linear 2; + animation: flip 100s -99.9s linear 2 paused; } </style> @@ -27,7 +27,13 @@ body { padding: 50px } <script> -document.getElementById("test").addEventListener("animationiteration", IterationListener, false); +document.getElementById("test").addEventListener("animationstart", StartListener, false); + +function StartListener(event) { + var test = document.getElementById("test"); + test.style.animationPlayState = 'running'; + test.addEventListener("animationiteration", IterationListener, false); +} function IterationListener(event) { setTimeout(RemoveReftestWait, 0); diff --git a/layout/reftests/transform-3d/animate-preserve3d-parent.html b/layout/reftests/transform-3d/animate-preserve3d-parent.html index ae3fec196..d05beac6f 100644 --- a/layout/reftests/transform-3d/animate-preserve3d-parent.html +++ b/layout/reftests/transform-3d/animate-preserve3d-parent.html @@ -18,7 +18,7 @@ body { padding: 50px } border: 1px solid black; transform-style: preserve-3d; /* use a -99.9s delay to start at 99.9% and then move to 0% */ - animation: spin 100s -99.9s linear 2; + animation: spin 100s -99.9s linear 2 paused; } #child { @@ -39,7 +39,13 @@ body { padding: 50px } <script> -document.getElementById("parent").addEventListener("animationiteration", IterationListener, false); +document.getElementById("parent").addEventListener("animationstart", StartListener, false); + +function StartListener(event) { + var test = document.getElementById("parent"); + test.style.animationPlayState = 'running'; + test.addEventListener("animationiteration", IterationListener, false); +} function IterationListener(event) { setTimeout(RemoveReftestWait, 0); diff --git a/layout/reftests/w3c-css/submitted/check-for-references.sh b/layout/reftests/w3c-css/submitted/check-for-references.sh index 977cee3f4..c30e18515 100755 --- a/layout/reftests/w3c-css/submitted/check-for-references.sh +++ b/layout/reftests/w3c-css/submitted/check-for-references.sh @@ -15,7 +15,7 @@ do else echo "Unexpected type $TYPE for $DIRNAME/$TEST" fi - if grep "rel=\"$REFTYPE\"" "$DIRNAME/$TEST" | head -1 | grep -q "href=\"$REF\"" + if grep "rel=\(\"$REFTYPE\"\|'$REFTYPE'\)" "$DIRNAME/$TEST" | head -1 | grep -q "href=\(\"$REF\"\|'$REF'\)" then #echo "Good link for $DIRNAME/$TEST" echo -n diff --git a/layout/reftests/w3c-css/submitted/text3/reftest.list b/layout/reftests/w3c-css/submitted/text3/reftest.list index 2712e4363..a1e3d70d0 100644 --- a/layout/reftests/w3c-css/submitted/text3/reftest.list +++ b/layout/reftests/w3c-css/submitted/text3/reftest.list @@ -5,4 +5,9 @@ == text-align-match-parent-root-ltr.html text-align-match-parent-root-ltr-ref.html == text-align-match-parent-root-rtl.html text-align-match-parent-root-rtl-ref.html +pref(layout.css.text-justify.enabled,true) == text-justify-none-001.html text-justify-none-001-ref.html +pref(layout.css.text-justify.enabled,true) == text-justify-inter-word-001.html text-justify-inter-word-001-ref.html +pref(layout.css.text-justify.enabled,true) == text-justify-inter-character-001.html text-justify-inter-character-001-ref.html +pref(layout.css.text-justify.enabled,true) == text-justify-distribute-001.html text-justify-inter-character-001-ref.html + == text-word-spacing-001.html text-word-spacing-ref.html diff --git a/layout/reftests/w3c-css/submitted/text3/text-justify-distribute-001.html b/layout/reftests/w3c-css/submitted/text3/text-justify-distribute-001.html new file mode 100644 index 000000000..4d29f0fee --- /dev/null +++ b/layout/reftests/w3c-css/submitted/text3/text-justify-distribute-001.html @@ -0,0 +1,28 @@ +<!DOCTYPE html> +<html> +<head> +<meta charset="utf-8"> +<title>CSS Text 7.4. Justification Method: text-justify: distribute</title> +<link rel="author" title="Chun-Min (Jeremy) Chen" href="mailto:jeremychen@mozilla.com"> +<link rel="author" title="Mozilla" href="https://www.mozilla.org"> +<link rel="help" href="https://drafts.csswg.org/css-text-3/#text-justify-property"> +<link rel='match' href='text-justify-inter-character-001-ref.html'> +<meta name="assert" content="text-justify:distribute means justification adjusts spacing between each pair of adjacent typographic character units."> +<style type='text/css'> +p { + font-size: 1.5em; + border: 1px solid black; + padding: 10px; + margin-right: 310px; +} +.test { + text-align-last: justify; + text-justify: distribute; +} +</style> +</head> +<body> +<p lang="en" class="test">XX</p> +<p lang="ja" class="test">文字</p> +</body> +</html> diff --git a/layout/reftests/w3c-css/submitted/text3/text-justify-inter-character-001-ref.html b/layout/reftests/w3c-css/submitted/text3/text-justify-inter-character-001-ref.html new file mode 100644 index 000000000..0a42a5cf8 --- /dev/null +++ b/layout/reftests/w3c-css/submitted/text3/text-justify-inter-character-001-ref.html @@ -0,0 +1,24 @@ +<!DOCTYPE html> +<html> +<head> +<meta charset="utf-8"> +<title>CSS Text 7.4. Justification Method: text-justify: inter-character</title> +<link rel="author" title="Chun-Min (Jeremy) Chen" href="mailto:jeremychen@mozilla.com"> +<link rel="author" title="Mozilla" href="https://www.mozilla.org"> +<style type='text/css'> +p { + font-size: 1.5em; + border: 1px solid black; + padding: 10px; + margin-right: 310px; +} +.right { + float: right; +} +</style> +</head> +<body> +<p lang="en">X<span class="right">X</span></p> +<p lang="ja">文<span class="right">字</span></p> +</body> +</html> diff --git a/layout/reftests/w3c-css/submitted/text3/text-justify-inter-character-001.html b/layout/reftests/w3c-css/submitted/text3/text-justify-inter-character-001.html new file mode 100644 index 000000000..639ab7ecb --- /dev/null +++ b/layout/reftests/w3c-css/submitted/text3/text-justify-inter-character-001.html @@ -0,0 +1,28 @@ +<!DOCTYPE html> +<html> +<head> +<meta charset="utf-8"> +<title>CSS Text 7.4. Justification Method: text-justify: inter-character</title> +<link rel="author" title="Chun-Min (Jeremy) Chen" href="mailto:jeremychen@mozilla.com"> +<link rel="author" title="Mozilla" href="https://www.mozilla.org"> +<link rel="help" href="https://drafts.csswg.org/css-text-3/#text-justify-property"> +<link rel='match' href='text-justify-inter-character-001-ref.html'> +<meta name="assert" content="text-justify:inter-character means justification adjusts spacing between each pair of adjacent typographic character units."> +<style type='text/css'> +p { + font-size: 1.5em; + border: 1px solid black; + padding: 10px; + margin-right: 310px; +} +.test { + text-align-last: justify; + text-justify: inter-character; +} +</style> +</head> +<body> +<p lang="en" class="test">XX</p> +<p lang="ja" class="test">文字</p> +</body> +</html> diff --git a/layout/reftests/w3c-css/submitted/text3/text-justify-inter-word-001-ref.html b/layout/reftests/w3c-css/submitted/text3/text-justify-inter-word-001-ref.html new file mode 100644 index 000000000..687e864e7 --- /dev/null +++ b/layout/reftests/w3c-css/submitted/text3/text-justify-inter-word-001-ref.html @@ -0,0 +1,25 @@ +<!DOCTYPE html> +<html> +<head> +<meta charset="utf-8"> +<title>CSS Text 7.4. Justification Method: text-justify: inter-word</title> +<link rel="author" title="Chun-Min (Jeremy) Chen" href="mailto:jeremychen@mozilla.com"> +<link rel="author" title="Mozilla" href="https://www.mozilla.org"> +<style type='text/css'> +p { + font-size: 1.5em; + border: 1px solid black; + padding: 10px; + margin-right: 310px; +} +.right { + float: right; +} +</style> +</head> +<body> +<p lang="en">Latin<span class="right">text</span></p> +<p lang="ja">日本<span class="right">文字</span></p> +<p lang="th">อักษรไทย<span class="right">อักษรไทย</span></p> +</body> +</html> diff --git a/layout/reftests/w3c-css/submitted/text3/text-justify-inter-word-001.html b/layout/reftests/w3c-css/submitted/text3/text-justify-inter-word-001.html new file mode 100644 index 000000000..75aec2f5f --- /dev/null +++ b/layout/reftests/w3c-css/submitted/text3/text-justify-inter-word-001.html @@ -0,0 +1,29 @@ +<!DOCTYPE html> +<html> +<head> +<meta charset="utf-8"> +<title>CSS Text 7.4. Justification Method: text-justify: inter-word</title> +<link rel="author" title="Chun-Min (Jeremy) Chen" href="mailto:jeremychen@mozilla.com"> +<link rel="author" title="Mozilla" href="https://www.mozilla.org"> +<link rel="help" href="https://drafts.csswg.org/css-text-3/#text-justify-property"> +<link rel='match' href='text-justify-inter-word-001-ref.html'> +<meta name="assert" content="text-justify:inter-word means justification adjusts spacing at word separators only."> +<style type='text/css'> +p { + font-size: 1.5em; + border: 1px solid black; + padding: 10px; + margin-right: 310px; +} +.test { + text-align-last: justify; + text-justify: inter-word; +} +</style> +</head> +<body> +<p lang="en" class="test">Latin text</p> +<p lang="ja" class="test">日本 文字</p> +<p lang="th" class="test">อักษรไทย อักษรไทย</p> +</body> +</html> diff --git a/layout/reftests/w3c-css/submitted/text3/text-justify-none-001-ref.html b/layout/reftests/w3c-css/submitted/text3/text-justify-none-001-ref.html new file mode 100644 index 000000000..c8500ac9f --- /dev/null +++ b/layout/reftests/w3c-css/submitted/text3/text-justify-none-001-ref.html @@ -0,0 +1,22 @@ +<!DOCTYPE html> +<html> +<head> +<meta charset="utf-8"> +<title>CSS Text 7.4. Justification Method: text-justify: none</title> +<link rel="author" title="Chun-Min (Jeremy) Chen" href="mailto:jeremychen@mozilla.com"> +<link rel="author" title="Mozilla" href="https://www.mozilla.org"> +<style type='text/css'> +p { + font-size: 1.5em; + border: 1px solid black; + padding: 10px; + margin-right: 310px; +} +</style> +</head> +<body> +<p lang="en">Latin text</p> +<p lang="ja">日本 文字</p> +<p lang="th">อักษรไทย อักษรไทย</p> +</body> +</html> diff --git a/layout/reftests/w3c-css/submitted/text3/text-justify-none-001.html b/layout/reftests/w3c-css/submitted/text3/text-justify-none-001.html new file mode 100644 index 000000000..2b5511195 --- /dev/null +++ b/layout/reftests/w3c-css/submitted/text3/text-justify-none-001.html @@ -0,0 +1,29 @@ +<!DOCTYPE html> +<html> +<head> +<meta charset="utf-8"> +<title>CSS Text 7.4. Justification Method: text-justify: none</title> +<link rel="author" title="Chun-Min (Jeremy) Chen" href="mailto:jeremychen@mozilla.com"> +<link rel="author" title="Mozilla" href="https://www.mozilla.org"> +<link rel="help" href="https://drafts.csswg.org/css-text-3/#text-justify-property"> +<link rel='match' href='text-justify-none-001-ref.html'> +<meta name="assert" content="text-justify:none means justification is disabled: there are no justification opportunities within the text."> +<style type='text/css'> +p { + font-size: 1.5em; + border: 1px solid black; + padding: 10px; + margin-right: 310px; +} +.test { + text-align-last: justify; + text-justify: none; +} +</style> +</head> +<body> +<p lang="en" class="test">Latin text</p> +<p lang="ja" class="test">日本 文字</p> +<p lang="th" class="test">อักษรไทย อักษรไทย</p> +</body> +</html> 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); |