mirror of
https://github.com/roytam1/palemoon27.git
synced 2026-05-26 14:18:48 +00:00
fffeb135bb
- Bug 1040668 part 2 - Parse and compute text emphasis properties. r=dbaron (0fb79d4709) - Bug 1040668 part 3 - Add helper function gfxTextRun::GetAdvanceForGlyph. r=jfkthame (f1cf02f5ff) - Bug 1216427 - Tests for backspacing over a character with variation selector, and over Regional Indicator flag symbols. r=emk (18957bfe77) - Bug 1216427 - part 1 - Ensure a character+VS sequence or a ligated Regional-Indicator flag symbol is deleted as a single unit when backspacing. r=emk (2776ff8f4a) - Bug 1216427 - part 2 - Ensure mouse selection does not split up a Regional Indicator flag symbol. r=emk (58eb82e6a1) - Bug 1040668 part 4 - Add helper function for ensuring a glyph is a complex glyph. r=jfkthame (ba17f7d0c4) - Bug 1040668 part 5 - Avoid unnecessary allocation inside EnsureComplexGlyph helper function. r=jfkthame (4968a7c68e) - Bug 1040668 part 6 - Add some specifier on gfxTextRun and gfxShapedWord so that compilers are able to reason out certain optimizations. r=jfkthame (69ca3eb959) - Bug 1040668 part 7 - Add NO_EMPHASIS_MARK flag in CompressedGlyph. r=jfkthame (34e9d8a6a6) - Bug 1227001 part 1 - Remove SetupBreakSinksFlags from BuildTextRunsScanner. r=jfkthame (520b1ba111) - Bug 1227001 part 2 - Remove no longer used mExistingTextRun from BreaSink. r=jfkthame (201782a78c) - Bug 1227001 part 3 - Remove no longer used mChangedBreaks from BreakSink. r=jfkthame (e754e2b13f) - Bug 1040668 part 8 - Setup text emphasis for text run. r=jfkthame (376377180e) - Bug 1040668 part 9 - Compute overflow from text-shadow after text decorations. r=dbaron (05c9bd08c2) - Bug 1040668 part 10 - Implement emphasis mark rendering. r=jfkthame (4d270afca8) - Bug 1040668 part 11 - Move line leadings adjusting code into a separate function in nsLineLayout. r=dholbert (fd4dd20ca5) - Bug 1040668 part 12 - Add line leadings for emphasis marks if necessary. r=dholbert (138add5ff7) - Bug 1040668 part 13 - Move first part of nsStyleFont::GetLanguage to nsPresContext::GetContentLanguage. r=dbaron (1e872d2a58) - Bug 1040668 part 14 - Add helper function nsStyleUtil::MatchesLanguagePrefix for doing simple language matching. r=dbaron (9322a02369) - Bug 1040668 part 15 - Make the default value of text-emphasis-position aware of the language. r=dbaron (6587c628da) - Bug 1040668 part 16 - Add reftests for text-emphasis. r=dbaron (9940d65182) - Bug 1040668 followup - Use monospace for text-emphasis reftests. rs=dbaron on a CLOSED TREE (fb7598c3ea) - Bug 1040668 followup 2 - Disable failing reftests of text-emphasis on Windows XP. (7c3f24ac22) - Bug 1040668 followup 3 - Wrap lang attribute mapping code in NS_STYLE_INHERIT_BIT test. r=dbaron (640e3b7b8f) - Bug 1219145 - nsRefreshDriver::IsJankCritical(). r=hiro To refine its alerts, Performance Stats API needs to be able to know whether a long-running operation is actually causing user-visible jank in the current process. This patch introduces a trivial API that lets clients ask the refresh driver whether any kind of animation is ongoing. (7c0868d7c4) - more missing XP theme stuff (637af0c6a0) - Bug 1210261. Tick root refresh driver last. r=mattwoodrow (dae1a325fa) - Bug 1221674 Part 2: Correct for negative content delay values. r=avih (9b9811c41c) - Bug 1211334 - Check if presshell is still available after dispatching transition events; r=mats (8a044a462c) - Bug 1211599 - Only allow whitelisted histograms to have > 100 buckets. r=nfroyd (d467e84130) - Bug 1219733 - Allow a 'bug_numbers' field in Histograms.json entries. r=vladan (8e63a713ec) - Bug 1222044 - Only allow lists of alert_emails r=vladan (1d5fcb009d) - Bug 1168263 - Add a flags parameter to GetResultingTransformMatrix instead of using bools. r=roc (ec5224f9af) - Bug 1168263 - Remove TransformRectOut since it's unused. r=roc (d20a79ae0e) - Bug 1215406 - Part 1: Remove NS_STYLE_ANIMATION_DIRECTION_XXX and NS_STYLE_ANIMATION_FILL_MODE_XXX. r=heycam (4bd7f8116b) - Bug 1215406 - Part 2: Change the types of direction and fillmode in StyleAnimation. r=heycam (e06323c81b) - Bug 1215406 - Part 3: Change the types of direction and fillmode in AnimationTiming. r=heycam (e5454d3ea5) - Bug 1215406 - Part 4: Add KeyframeEffectOptions. r=smaug Add KeyframeEffectOptions in KeyframeEffect.webidl (0ce3372fd1) - Bug 1215406 - Part 5: Implement KeyframeEffectOptions in KeyframeEffectReadOnly constructor. r=birtles (a472e9ac5f) - tch 2 - Use an enum class for NS_STYLE_BOX_SIZING_*. r=heycam (4897b7b8ff) - Bug 1218195, mark MutationObserver as observing in all the nested DOM mutations, r=bz (48a4aa8a91) - Bug 1172870 - Part 3 - Fix openWindow mochitest to work on e10s (574cc6fa4d) - Bug 1223265 - Fix -Wunreachable-code and -Wimplicit-fallthrough warnings in dom/bindings and dom/ipc. r=khuey (9eab632140) - Bug 1189195 - Fix PContentPermissionRequest shutdown () r=fabrice (6bc1a681f5) - Bug 1210508 - Handle null OriginAttributes from JS-implemented nsILoadContext. r=me (2f71edb9d7) - Bug 1224596 part 1. Add a version of WorkerMainThreadRunnable::Dispatch that takes an ErrorResult to report failure to dispatch on. r=khuey (e60a0fb115) - Bug 1224596 part 2. Switch Navigator to using the new WorkerMainThreadRunnable::Dispatch signature. r=khuey (d9298a4763) - Bug 1224596 part 3. Make some WebSocket(Impl) methods whose return value is totally ignored void. r=khuey (1d04b52d44) - Bug 1224596 part 4. Switch WebSocket to using the new WorkerMainThreadRunnable::Dispatch signature. r=khuey (aa06e6417b) - Bug 1224596 part 5. Switch nsPerformance to using the new WorkerMainThreadRunnable::Dispatch signature. r=khuey (0303b7de09) - Bug 1224596 part 6. Switch BroadcastChannel to using the new WorkerMainThreadRunnable::Dispatch signature. r=khuey (feacc21f63) - Bug 1224636. Fix some code in ImageBitmap that rejects a promise with an ErrorResult, then keeps trying to use that ErrorResult. r=kaku (89b769fe07) - Bug 1224596 part 7. Switch ImageBitmap to using the new WorkerMainThreadRunnable::Dispatch signature. r=khuey (e21b0a4a0a) - Bug 1183954, Don't leak nsStructuredCloneContainer (use of 'auto' is error prone), r=leak (1a9b30f2ca) - Bug 1114554 - Patch 9 - Fixed crash in b2g-desktop tests. r=nsm (6b12c6d121) - Bug 1189090 - Rework the nsISupports implementation in the ScopeCheckingGetCallback and its subclasses; r=nsm (6bea544597) - Bug 1187018 - Ensure feature is nulled out if it does not get added. r=khuey (9d7439498a) - Bug 1224596 part 8. Switch Notification to using the new WorkerMainThreadRunnable::Dispatch signature. r=khuey (03f0f6877f) - Bug 1196079 - Always try to release Notification via normal WorkerRunnable first. r=wchen (c093253d6f) - Bug 1203324 - disable notifications on serviceworkers. r=ehsan,wchen (aa39310dc1) - Bug 1199901 - GetOrigin() fails cleanly instead of asserting principal. r=wchen (148c634a4f) - Bug 1199901 - Clear mObserver when WorkerNotificationObserver is destroyed. r=wchen (6d5cd99183) - Bug 1199901 - Bustage fix due to rebase. a=bustage (344bd62af5) - Bug 1225470 Report a message to the console when a service worker waitUntil() is rejected. r=baku (610da2eec6) - Bug 1217909 P1 Report service worker exceptions to controlled documents. r=catalinb (f8bd4677d5) - Bug 1216566 - Fix a bug in nsIServiceWorkerManager.getAllRegistrations;r=catalinb (ed3e14ab43) - Bug 1219205 - ServiceWorkerInfo should be an XPCOM object;r=catalinb (677a6f1ffd) - Bug 1217909 P2 Track registering documents as weak reference so SWM can report errors to them. r=catalinb (4233dc3edc) - Bug 1217909 P3 Refactor service worker register()/update() to reject only with SecurityErr or TypeErr. r=catalinb (c6891a7fae) - Bug 1220740 - nsIServiceWorkerRegistrationInfo should emit an event when its scriptSpec property changes;r=amarchesini (5772bb5914) - Bug 1207727 - Add WPT tests for service worker update algorithm. r=bkelly (a4812571a1) - Bug 1217367 - Service workers update algorithm optimization. r=bkelly (e377debad1) - Bug 1226479. Change ErrorResult::ThrowTypeError/ThrowRangeError to take string references, not pointers. r=mccr8 (0804899666) - Bug 1224659 - Worker DataStore code should not use ErrorResult cross threads, r=bz (aef03b0fdd) - Bug 1224596 part 9. Switch DataStore to using the new WorkerMainThreadRunnable::Dispatch signature. r=khuey (1eb1c427f2) - Bug 1224596 part 10. Switch DataStoreCursor to using the new WorkerMainThreadRunnable::Dispatch signature. r=khuey (114af8021b) - Bug 1224596 part 11. Switch WorkerNavigator to using the new WorkerMainThreadRunnable::Dispatch signature. r=khuey (b2347a0c49) - Bug 1224596 part 12. Switch ServiceWorkerRegistration to using the new WorkerMainThreadRunnable::Dispatch signature. r=khuey (d1ff076836) - Bug 1224596 part 13. Switch gfxUtils to using the new WorkerMainThreadRunnable::Dispatch signature. r=khuey (a523997588) - Bug 1224007 part 1. Rename ThrowMethodFailed to MaybeSetPendingException and make it an ErrorResult instance method. r=peterv (bdf0891f54) - Bug 1224007 part 2. Make the various ErrorResult::Report* methods private, so consumers all go through MaybeSetPendingException and rename them to more clearly indicate what they're actually doing. r=peterv (48f483c153) - Bug 1203151 - Allow disabling of screen wakelocks for video elements. r=baku (6bd9ff6916) - Bug 1224007 part 4. Fix some cases in which ErrorResult instances are destroyed without doing anything useful with exceptions on them. r=peterv (e328785e5d) - Bug 1224007 part 5. Get rid of ErrorResult::StealJSException. r=peterv (db19cfb31e) - Bug 1224007 part 3. Push down WouldReportJSException into MaybeSetPendingException, since anyone calling the latter will propagate the JS exception as needed. r=peterv (1d3b7b415d) - Bug 1224007 part 6. Change MaybeSetPendingException to set the ErrorResult state to "not failed", just like SuppressException and StealNSResult already do, and assert in the destructor that the ErrorResult is not Failed(). (a028838e8d) - Bug 1213815 - Update URLSearchParams and URLUtils in webidl files, r=bz (28fb8f7de5) - Bug 1213815 - dom/webidl/HTMLHyperlinkElementUtils and URL don't need to throw exceptions as we did before, r=bz (b13dc3bcb9) - Bug 1224596 part 14. Switch URL to using the new WorkerMainThreadRunnable::Dispatch signature. r=baku (232677e50e) - fix updating the backport (9e958da5ca) - Bug 1224596 part 16. Switch Fetch to using the new WorkerMainThreadRunnable::Dispatch signature. r=bkelly (939e338f22) - Bug 1224596 part 17. Remove the old WorkerMainThreadRunnable::Dispatch signature. r=khuey (5a70429ec8) - Bug 1143575. Don't report negative frame delays. r=cpearce (7d8bc0f753) - Bug 1187371 - Get rid of dom.broadcastChannel.enabled pref, r=bz (9335b7ae90) - Bug 1196514 - remove dom.messagechannel.enabled pref, r=smaug (31e06119b4) - Bug 1166356 - Properly detect double-caching in nsXULPrototypeCache; r=ehsan (29df9ffb2d) - Bug 1168916 - Get rid of redundant pref callback in nsXULPrototypeCache; r=janv (9f37fff405) - Bug 1139099: Dispatch DOMDocElementInserted to match the document-element-inserted observer notification. r=mrbkap (6565e4b924) - Bug 1187068 - Tell the cycle collector about nsContentSink::mCSSLoader. r=heycam (4ae23eb26c) - Bug 1172189 - Fix overflow in nsXULContentSink.cpp. r=ehsan (cc6330f5de) - Bug 1126010 - XULContentSinkImpl::mParser should be an nsRefPtr. r=smaug (d6bb567692) - Bug 1147946, part 7 - Remove trailing whitespace from nsXULContentSink.cpp. r=baku (cdcadbfeeb) - Bug 1147946, part 1 - Tuck elses in nsXULContentSink.cpp. r=baku (26fd806676) - Bug 1147946, part 2 - Move body of check inside prior if in XULContentSinkImpl::OpenScript(). r=baku (b509455bdb) - Bug 1147946, part 3 - Eliminate unused case for non-JS scripting languages in XULContentSinkImpl::OpenScript(). r=baku (4136933cc2) - Bug 1147946, part 4 - Use an early return in XULContentSinkImpl::OpenScript(). r=baku (c3e293474b) - Bug 1147946, part 5 - Don't use the generic nsIProgrammingLanguage enum in XULContentSinkImpl::OpenScript(). r=baku (89a124e23f) - Bug 1147946, part 6 - Remove some useless null checks on infallible new in XULContentSinkImpl. r=baku (1ac57e8c3a) - Bug 1221351 P1 ServiceWorkerContainer and ServiceWorkerRegistration should not crash for null window owner. r=catalinb (1a72748632) - Bug 1212867 - Node.isEqualNode() should ignore internal subsets; r=bzBug (99b166ffee) - Bug 492933 - getElementsByTagName should match on localName not tagName, r=smaug (d0c6ceabf1) - Bug 912470 part 1 - Implement Encoding Standard-compliant big5 decoder. r=emk. (c680b0ae9b) - Bug 1170932: Test handling of unmapped characters in unicode-to-codepage encoders (ca36bcbd35) - fix style (95a90bfe3a) - Bug 1170794 - patch 2 - Improve the length check of the input in nsUnicode*::GetMaxLength, r=dveditz (aa864d656f) - Bug 1170794 followup: Add 'override' annotations to Convert() & Reset() methods in intl/uconv. rs=ehsan (bb3e6e492e) - Bug 1176462 - Remove nsTableDecoderSupport. r=smontagu (f4a86c44b3) - Bug 1169248 - Fix GBK/GB18030 encoders. r=smontagu (ed946e1ee1) - Bug 1155539 - Remove obsolete encoding decoder telemetry probes. r=emk. (44e15bfb40) - Bug 912470 part 2 - Implement Encoding Standard-compliant big5 encoder. r=emk. (5cca2dc4a0) - Bug 912470 addendum - Pass override static analysis. r=emk. (c163bffeb4) - Bug 1170932: Improve error handling for the gbk encoder, r=emk (30e95b34a5) - Bug 1202366 - Implement the encoder error mode "HTML" for nsFormSubmission without nsISaveAsCharset. r=NOT_emk. (ebc8b542dd) - update manifest (6cc19172cc) - Bug 1197309 - remove PR_snprintf calls in intl/; r=froydnj (79fcdfa845) - Bug 1214619 - Remove nsISaveAsCharset as much as possible without breaking extensions in popular use. r=emk. (89b71b3d87) - Bug 1214857. Store the document-is-HTML state directly in nsContentList instead of refetching from the node being matched. r=smaug (4c4fbf469e) - Bug 1221351 P2 Add a web-platform-test to check for crash when calling .register() on closed window. r=catalinb (eeb30c1bba) - Bug 1221351 P3 Fix test name in register-closed-window.https.html. a=testonly (5bfa840044) - Bug 1224436: Remove enumerator usage in ServiceWorkerManager.cpp. r=njn (b8cb094d3c) - Bug 1223716. Make HTMLCollection check for the element being HTML before checking for its name inside its named getter. r=bkelly (977e0bff5a) - Bug 1180737 - Add update-test.py and update test to latest version. r=bkelly. (59faa36d5c) - Bug 1217909 P4 Extend wpt tests to verify update() promise values for different script failures. r=catalinb (a2f7352a3a) - Bug 1217909 P5 Add wpt test case for fetch event handlers that throw. r=catalinb (56a77f611c) - Bug 1217909 P6 Fix wpt registration.https.html to expect TypeError for script evaluation errors. r=catalinb (3de8a45688) - Bug 1217909 P7 Fix mochitest to expect TypeError when serviceWorker.register() rejects. r=catalinb (6e8841c41e) - Bug 1217909 P8 Track navigation interceptions per scope in ServiceWorkerManager. r=catalinb (6705ba8337) - Bug 1217909 P9 Report exceptions to windows performing an intercepted navigation. r=catalinb (52f9fece14) - Bug 1217909 P10 Remove stale nsTArray when the last registering document for a scope is removed. r=catalinb (b739bcc3b2) - Bug 1217909 P11 Only report errors to documents that are active and not in the bfcache. r=catalinb (5ffd633af2) - Bug 1223378 Tighten service worker register() principal checks. r=baku (478785f2cc) - Bug 1189685 - Part 1: Ensure that the state of all ServiceWorker instances is up to date when dispatching statechange events; r=bkelly (2bd9b78c58) - Bug 1189685 - Part 2: Make synced-state.https.html pass; r=bkelly (029f942d8c) - Bug 1220740 - nsIServiceWorkerRegistrationInfo should emit an event when its worker properties change;r=amarchesini (8243a3debc) - Bug 1186856 ServiceWorker .register() should always stop current registration from uninstalling. r=jdm (ec7d6e0e7c) - Bug 1224941 Don't crash during ServiceWorker life cycle event dispatch if window is gone. r=baku (a3f45af3e4) - Bug 1180754 - Get serviceworkerobject-scripturl test passing. r=bkelly (c7979bef47) - Bug 1201498 - Service worker update should compare scriptURL to worker URL without fragment, r=bkelly (914f630528) - Bug 594505 - Remove obsolete comment since this bug has now been fixed. r=me DONTBUILD (44f3a15b91) - Bug 1221840. Support repeating images in 1 axis. r=seth (449ea3e97e) - const-var (5433688051) - Bug 1574573 - Disambiguate a use of Handle in XPCShellEnvironment.cpp r=Ehsan (15b44177d1) - clean up warnings (6e64313d0c)
2016 lines
68 KiB
C++
2016 lines
68 KiB
C++
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
|
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
|
|
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
|
* You can obtain one at http://mozilla.org/MPL/2.0/. */
|
|
|
|
#include "mozilla/dom/KeyframeEffect.h"
|
|
|
|
#include "mozilla/dom/AnimationEffectReadOnlyBinding.h"
|
|
#include "mozilla/dom/KeyframeEffectBinding.h"
|
|
#include "mozilla/dom/PropertyIndexedKeyframesBinding.h"
|
|
#include "mozilla/FloatingPoint.h"
|
|
#include "mozilla/LookAndFeel.h" // For LookAndFeel::GetInt
|
|
#include "mozilla/StyleAnimationValue.h"
|
|
#include "AnimationCommon.h"
|
|
#include "Layers.h" // For Layer
|
|
#include "nsCSSParser.h"
|
|
#include "nsCSSPropertySet.h"
|
|
#include "nsCSSProps.h" // For nsCSSProps::PropHasFlags
|
|
#include "nsCSSValue.h"
|
|
#include "nsStyleUtil.h"
|
|
#include <algorithm> // std::max
|
|
|
|
namespace mozilla {
|
|
|
|
bool
|
|
AnimationTiming::FillsForwards() const
|
|
{
|
|
return mFillMode == dom::FillMode::Both ||
|
|
mFillMode == dom::FillMode::Forwards;
|
|
}
|
|
|
|
bool
|
|
AnimationTiming::FillsBackwards() const
|
|
{
|
|
return mFillMode == dom::FillMode::Both ||
|
|
mFillMode == dom::FillMode::Backwards;
|
|
}
|
|
|
|
// Helper functions for generating a ComputedTimingProperties dictionary
|
|
static void
|
|
GetComputedTimingDictionary(const ComputedTiming& aComputedTiming,
|
|
const Nullable<TimeDuration>& aLocalTime,
|
|
const AnimationTiming& aTiming,
|
|
dom::ComputedTimingProperties& aRetVal)
|
|
{
|
|
// AnimationEffectTimingProperties
|
|
aRetVal.mDelay = aTiming.mDelay.ToMilliseconds();
|
|
aRetVal.mFill = aTiming.mFillMode;
|
|
aRetVal.mIterations = aTiming.mIterationCount;
|
|
aRetVal.mDuration.SetAsUnrestrictedDouble() = aTiming.mIterationDuration.ToMilliseconds();
|
|
aRetVal.mDirection = aTiming.mDirection;
|
|
|
|
// ComputedTimingProperties
|
|
aRetVal.mActiveDuration = aComputedTiming.mActiveDuration.ToMilliseconds();
|
|
aRetVal.mEndTime
|
|
= std::max(aRetVal.mDelay + aRetVal.mActiveDuration + aRetVal.mEndDelay, 0.0);
|
|
aRetVal.mLocalTime = dom::AnimationUtils::TimeDurationToDouble(aLocalTime);
|
|
aRetVal.mProgress = aComputedTiming.mProgress;
|
|
if (!aRetVal.mProgress.IsNull()) {
|
|
// Convert the returned currentIteration into Infinity if we set
|
|
// (uint64_t) aComputedTiming.mCurrentIteration to UINT64_MAX
|
|
double iteration = aComputedTiming.mCurrentIteration == UINT64_MAX
|
|
? PositiveInfinity<double>()
|
|
: static_cast<double>(aComputedTiming.mCurrentIteration);
|
|
aRetVal.mCurrentIteration.SetValue(iteration);
|
|
}
|
|
}
|
|
|
|
namespace dom {
|
|
|
|
NS_IMPL_CYCLE_COLLECTION_INHERITED(KeyframeEffectReadOnly,
|
|
AnimationEffectReadOnly,
|
|
mTarget,
|
|
mAnimation)
|
|
|
|
NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN_INHERITED(KeyframeEffectReadOnly,
|
|
AnimationEffectReadOnly)
|
|
NS_IMPL_CYCLE_COLLECTION_TRACE_END
|
|
|
|
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(KeyframeEffectReadOnly)
|
|
NS_INTERFACE_MAP_END_INHERITING(AnimationEffectReadOnly)
|
|
|
|
NS_IMPL_ADDREF_INHERITED(KeyframeEffectReadOnly, AnimationEffectReadOnly)
|
|
NS_IMPL_RELEASE_INHERITED(KeyframeEffectReadOnly, AnimationEffectReadOnly)
|
|
|
|
KeyframeEffectReadOnly::KeyframeEffectReadOnly(
|
|
nsIDocument* aDocument,
|
|
Element* aTarget,
|
|
nsCSSPseudoElements::Type aPseudoType,
|
|
const AnimationTiming& aTiming)
|
|
: AnimationEffectReadOnly(aDocument)
|
|
, mTarget(aTarget)
|
|
, mTiming(aTiming)
|
|
, mPseudoType(aPseudoType)
|
|
{
|
|
MOZ_ASSERT(aTarget, "null animation target is not yet supported");
|
|
ResetIsRunningOnCompositor();
|
|
}
|
|
|
|
JSObject*
|
|
KeyframeEffectReadOnly::WrapObject(JSContext* aCx,
|
|
JS::Handle<JSObject*> aGivenProto)
|
|
{
|
|
return KeyframeEffectReadOnlyBinding::Wrap(aCx, this, aGivenProto);
|
|
}
|
|
|
|
IterationCompositeOperation
|
|
KeyframeEffectReadOnly::IterationComposite() const
|
|
{
|
|
return IterationCompositeOperation::Replace;
|
|
}
|
|
|
|
CompositeOperation
|
|
KeyframeEffectReadOnly::Composite() const
|
|
{
|
|
return CompositeOperation::Replace;
|
|
}
|
|
|
|
void
|
|
KeyframeEffectReadOnly::SetTiming(const AnimationTiming& aTiming)
|
|
{
|
|
if (mTiming == aTiming) {
|
|
return;
|
|
}
|
|
mTiming = aTiming;
|
|
if (mAnimation) {
|
|
mAnimation->NotifyEffectTimingUpdated();
|
|
}
|
|
}
|
|
|
|
Nullable<TimeDuration>
|
|
KeyframeEffectReadOnly::GetLocalTime() const
|
|
{
|
|
// Since the *animation* start time is currently always zero, the local
|
|
// time is equal to the parent time.
|
|
Nullable<TimeDuration> result;
|
|
if (mAnimation) {
|
|
result = mAnimation->GetCurrentTime();
|
|
}
|
|
return result;
|
|
}
|
|
|
|
void
|
|
KeyframeEffectReadOnly::GetComputedTimingAsDict(ComputedTimingProperties& aRetVal) const
|
|
{
|
|
const Nullable<TimeDuration> currentTime = GetLocalTime();
|
|
GetComputedTimingDictionary(GetComputedTimingAt(currentTime, mTiming),
|
|
currentTime,
|
|
mTiming,
|
|
aRetVal);
|
|
}
|
|
|
|
ComputedTiming
|
|
KeyframeEffectReadOnly::GetComputedTimingAt(
|
|
const Nullable<TimeDuration>& aLocalTime,
|
|
const AnimationTiming& aTiming)
|
|
{
|
|
const TimeDuration zeroDuration;
|
|
|
|
// Currently we expect negative durations to be picked up during CSS
|
|
// parsing but when we start receiving timing parameters from other sources
|
|
// we will need to clamp negative durations here.
|
|
// For now, if we're hitting this it probably means we're overflowing
|
|
// integer arithmetic in mozilla::TimeStamp.
|
|
MOZ_ASSERT(aTiming.mIterationDuration >= zeroDuration,
|
|
"Expecting iteration duration >= 0");
|
|
|
|
// Always return the same object to benefit from return-value optimization.
|
|
ComputedTiming result;
|
|
|
|
result.mActiveDuration = ActiveDuration(aTiming);
|
|
|
|
// The default constructor for ComputedTiming sets all other members to
|
|
// values consistent with an animation that has not been sampled.
|
|
if (aLocalTime.IsNull()) {
|
|
return result;
|
|
}
|
|
const TimeDuration& localTime = aLocalTime.Value();
|
|
|
|
// When we finish exactly at the end of an iteration we need to report
|
|
// the end of the final iteration and not the start of the next iteration
|
|
// so we set up a flag for that case.
|
|
bool isEndOfFinalIteration = false;
|
|
|
|
// Get the normalized time within the active interval.
|
|
StickyTimeDuration activeTime;
|
|
if (localTime >= aTiming.mDelay + result.mActiveDuration) {
|
|
result.mPhase = ComputedTiming::AnimationPhase::After;
|
|
if (!aTiming.FillsForwards()) {
|
|
// The animation isn't active or filling at this time.
|
|
result.mProgress.SetNull();
|
|
return result;
|
|
}
|
|
activeTime = result.mActiveDuration;
|
|
// Note that infinity == floor(infinity) so this will also be true when we
|
|
// have finished an infinitely repeating animation of zero duration.
|
|
isEndOfFinalIteration =
|
|
aTiming.mIterationCount != 0.0 &&
|
|
aTiming.mIterationCount == floor(aTiming.mIterationCount);
|
|
} else if (localTime < aTiming.mDelay) {
|
|
result.mPhase = ComputedTiming::AnimationPhase::Before;
|
|
if (!aTiming.FillsBackwards()) {
|
|
// The animation isn't active or filling at this time.
|
|
result.mProgress.SetNull();
|
|
return result;
|
|
}
|
|
// activeTime is zero
|
|
} else {
|
|
MOZ_ASSERT(result.mActiveDuration != zeroDuration,
|
|
"How can we be in the middle of a zero-duration interval?");
|
|
result.mPhase = ComputedTiming::AnimationPhase::Active;
|
|
activeTime = localTime - aTiming.mDelay;
|
|
}
|
|
|
|
// Get the position within the current iteration.
|
|
StickyTimeDuration iterationTime;
|
|
if (aTiming.mIterationDuration != zeroDuration) {
|
|
iterationTime = isEndOfFinalIteration
|
|
? StickyTimeDuration(aTiming.mIterationDuration)
|
|
: activeTime % aTiming.mIterationDuration;
|
|
} /* else, iterationTime is zero */
|
|
|
|
// Determine the 0-based index of the current iteration.
|
|
if (isEndOfFinalIteration) {
|
|
result.mCurrentIteration =
|
|
aTiming.mIterationCount == NS_IEEEPositiveInfinity()
|
|
? UINT64_MAX // In GetComputedTimingDictionary(), we will convert this
|
|
// into Infinity.
|
|
: static_cast<uint64_t>(aTiming.mIterationCount) - 1;
|
|
} else if (activeTime == zeroDuration) {
|
|
// If the active time is zero we're either in the first iteration
|
|
// (including filling backwards) or we have finished an animation with an
|
|
// iteration duration of zero that is filling forwards (but we're not at
|
|
// the exact end of an iteration since we deal with that above).
|
|
result.mCurrentIteration =
|
|
result.mPhase == ComputedTiming::AnimationPhase::After
|
|
? static_cast<uint64_t>(aTiming.mIterationCount) // floor
|
|
: 0;
|
|
} else {
|
|
result.mCurrentIteration =
|
|
static_cast<uint64_t>(activeTime / aTiming.mIterationDuration); // floor
|
|
}
|
|
|
|
// Normalize the iteration time into a fraction of the iteration duration.
|
|
if (result.mPhase == ComputedTiming::AnimationPhase::Before) {
|
|
result.mProgress.SetValue(0.0);
|
|
} else if (result.mPhase == ComputedTiming::AnimationPhase::After) {
|
|
double progress = isEndOfFinalIteration
|
|
? 1.0
|
|
: fmod(aTiming.mIterationCount, 1.0f);
|
|
result.mProgress.SetValue(progress);
|
|
} else {
|
|
// We are in the active phase so the iteration duration can't be zero.
|
|
MOZ_ASSERT(aTiming.mIterationDuration != zeroDuration,
|
|
"In the active phase of a zero-duration animation?");
|
|
double progress = aTiming.mIterationDuration == TimeDuration::Forever()
|
|
? 0.0
|
|
: iterationTime / aTiming.mIterationDuration;
|
|
result.mProgress.SetValue(progress);
|
|
}
|
|
|
|
bool thisIterationReverse = false;
|
|
switch (aTiming.mDirection) {
|
|
case PlaybackDirection::Normal:
|
|
thisIterationReverse = false;
|
|
break;
|
|
case PlaybackDirection::Reverse:
|
|
thisIterationReverse = true;
|
|
break;
|
|
case PlaybackDirection::Alternate:
|
|
thisIterationReverse = (result.mCurrentIteration & 1) == 1;
|
|
break;
|
|
case PlaybackDirection::Alternate_reverse:
|
|
thisIterationReverse = (result.mCurrentIteration & 1) == 0;
|
|
break;
|
|
default:
|
|
MOZ_ASSERT(true, "Unknown PlaybackDirection type");
|
|
}
|
|
if (thisIterationReverse) {
|
|
result.mProgress.SetValue(1.0 - result.mProgress.Value());
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
StickyTimeDuration
|
|
KeyframeEffectReadOnly::ActiveDuration(const AnimationTiming& aTiming)
|
|
{
|
|
if (aTiming.mIterationCount == mozilla::PositiveInfinity<float>()) {
|
|
// An animation that repeats forever has an infinite active duration
|
|
// unless its iteration duration is zero, in which case it has a zero
|
|
// active duration.
|
|
const StickyTimeDuration zeroDuration;
|
|
return aTiming.mIterationDuration == zeroDuration
|
|
? zeroDuration
|
|
: StickyTimeDuration::Forever();
|
|
}
|
|
return StickyTimeDuration(
|
|
aTiming.mIterationDuration.MultDouble(aTiming.mIterationCount));
|
|
}
|
|
|
|
// https://w3c.github.io/web-animations/#in-play
|
|
bool
|
|
KeyframeEffectReadOnly::IsInPlay() const
|
|
{
|
|
if (!mAnimation || mAnimation->PlayState() == AnimationPlayState::Finished) {
|
|
return false;
|
|
}
|
|
|
|
return GetComputedTiming().mPhase == ComputedTiming::AnimationPhase::Active;
|
|
}
|
|
|
|
// https://w3c.github.io/web-animations/#current
|
|
bool
|
|
KeyframeEffectReadOnly::IsCurrent() const
|
|
{
|
|
if (!mAnimation || mAnimation->PlayState() == AnimationPlayState::Finished) {
|
|
return false;
|
|
}
|
|
|
|
ComputedTiming computedTiming = GetComputedTiming();
|
|
return computedTiming.mPhase == ComputedTiming::AnimationPhase::Before ||
|
|
computedTiming.mPhase == ComputedTiming::AnimationPhase::Active;
|
|
}
|
|
|
|
// https://w3c.github.io/web-animations/#in-effect
|
|
bool
|
|
KeyframeEffectReadOnly::IsInEffect() const
|
|
{
|
|
ComputedTiming computedTiming = GetComputedTiming();
|
|
return !computedTiming.mProgress.IsNull();
|
|
}
|
|
|
|
void
|
|
KeyframeEffectReadOnly::SetAnimation(Animation* aAnimation)
|
|
{
|
|
mAnimation = aAnimation;
|
|
}
|
|
|
|
const AnimationProperty*
|
|
KeyframeEffectReadOnly::GetAnimationOfProperty(nsCSSProperty aProperty) const
|
|
{
|
|
for (size_t propIdx = 0, propEnd = mProperties.Length();
|
|
propIdx != propEnd; ++propIdx) {
|
|
if (aProperty == mProperties[propIdx].mProperty) {
|
|
const AnimationProperty* result = &mProperties[propIdx];
|
|
if (!result->mWinsInCascade) {
|
|
result = nullptr;
|
|
}
|
|
return result;
|
|
}
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
bool
|
|
KeyframeEffectReadOnly::HasAnimationOfProperties(
|
|
const nsCSSProperty* aProperties,
|
|
size_t aPropertyCount) const
|
|
{
|
|
for (size_t i = 0; i < aPropertyCount; i++) {
|
|
if (HasAnimationOfProperty(aProperties[i])) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void
|
|
KeyframeEffectReadOnly::ComposeStyle(RefPtr<AnimValuesStyleRule>& aStyleRule,
|
|
nsCSSPropertySet& aSetProperties)
|
|
{
|
|
ComputedTiming computedTiming = GetComputedTiming();
|
|
|
|
// If the progress is null, we don't have fill data for the current
|
|
// time so we shouldn't animate.
|
|
if (computedTiming.mProgress.IsNull()) {
|
|
return;
|
|
}
|
|
|
|
MOZ_ASSERT(!computedTiming.mProgress.IsNull() &&
|
|
0.0 <= computedTiming.mProgress.Value() &&
|
|
computedTiming.mProgress.Value() <= 1.0,
|
|
"iteration progress should be in [0-1]");
|
|
|
|
for (size_t propIdx = 0, propEnd = mProperties.Length();
|
|
propIdx != propEnd; ++propIdx)
|
|
{
|
|
const AnimationProperty& prop = mProperties[propIdx];
|
|
|
|
MOZ_ASSERT(prop.mSegments[0].mFromKey == 0.0, "incorrect first from key");
|
|
MOZ_ASSERT(prop.mSegments[prop.mSegments.Length() - 1].mToKey == 1.0,
|
|
"incorrect last to key");
|
|
|
|
if (aSetProperties.HasProperty(prop.mProperty)) {
|
|
// Animations are composed by AnimationCollection by iterating
|
|
// from the last animation to first. For animations targetting the
|
|
// same property, the later one wins. So if this property is already set,
|
|
// we should not override it.
|
|
continue;
|
|
}
|
|
|
|
if (!prop.mWinsInCascade) {
|
|
// This isn't the winning declaration, so don't add it to style.
|
|
// For transitions, this is important, because it's how we
|
|
// implement the rule that CSS transitions don't run when a CSS
|
|
// animation is running on the same property and element. For
|
|
// animations, this is only skipping things that will otherwise be
|
|
// overridden.
|
|
continue;
|
|
}
|
|
|
|
aSetProperties.AddProperty(prop.mProperty);
|
|
|
|
MOZ_ASSERT(prop.mSegments.Length() > 0,
|
|
"property should not be in animations if it has no segments");
|
|
|
|
// FIXME: Maybe cache the current segment?
|
|
const AnimationPropertySegment *segment = prop.mSegments.Elements(),
|
|
*segmentEnd = segment + prop.mSegments.Length();
|
|
while (segment->mToKey < computedTiming.mProgress.Value()) {
|
|
MOZ_ASSERT(segment->mFromKey < segment->mToKey, "incorrect keys");
|
|
++segment;
|
|
if (segment == segmentEnd) {
|
|
MOZ_ASSERT_UNREACHABLE("incorrect iteration progress");
|
|
break; // in order to continue in outer loop (just below)
|
|
}
|
|
MOZ_ASSERT(segment->mFromKey == (segment-1)->mToKey, "incorrect keys");
|
|
}
|
|
if (segment == segmentEnd) {
|
|
continue;
|
|
}
|
|
MOZ_ASSERT(segment->mFromKey < segment->mToKey, "incorrect keys");
|
|
MOZ_ASSERT(segment >= prop.mSegments.Elements() &&
|
|
size_t(segment - prop.mSegments.Elements()) <
|
|
prop.mSegments.Length(),
|
|
"out of array bounds");
|
|
|
|
if (!aStyleRule) {
|
|
// Allocate the style rule now that we know we have animation data.
|
|
aStyleRule = new AnimValuesStyleRule();
|
|
}
|
|
|
|
double positionInSegment =
|
|
(computedTiming.mProgress.Value() - segment->mFromKey) /
|
|
(segment->mToKey - segment->mFromKey);
|
|
double valuePosition =
|
|
segment->mTimingFunction.GetValue(positionInSegment);
|
|
|
|
StyleAnimationValue *val = aStyleRule->AddEmptyValue(prop.mProperty);
|
|
|
|
#ifdef DEBUG
|
|
bool result =
|
|
#endif
|
|
StyleAnimationValue::Interpolate(prop.mProperty,
|
|
segment->mFromValue,
|
|
segment->mToValue,
|
|
valuePosition, *val);
|
|
MOZ_ASSERT(result, "interpolate must succeed now");
|
|
}
|
|
}
|
|
|
|
bool
|
|
KeyframeEffectReadOnly::IsPropertyRunningOnCompositor(
|
|
nsCSSProperty aProperty) const
|
|
{
|
|
const auto& info = LayerAnimationInfo::sRecords;
|
|
for (size_t i = 0; i < ArrayLength(mIsPropertyRunningOnCompositor); i++) {
|
|
if (info[i].mProperty == aProperty) {
|
|
return mIsPropertyRunningOnCompositor[i];
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool
|
|
KeyframeEffectReadOnly::IsRunningOnCompositor() const
|
|
{
|
|
// We consider animation is running on compositor if there is at least
|
|
// one property running on compositor.
|
|
// Animation.IsRunningOnCompotitor will return more fine grained
|
|
// information in bug 1196114.
|
|
for (bool isPropertyRunningOnCompositor : mIsPropertyRunningOnCompositor) {
|
|
if (isPropertyRunningOnCompositor) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void
|
|
KeyframeEffectReadOnly::SetIsRunningOnCompositor(nsCSSProperty aProperty,
|
|
bool aIsRunning)
|
|
{
|
|
static_assert(
|
|
MOZ_ARRAY_LENGTH(LayerAnimationInfo::sRecords) ==
|
|
MOZ_ARRAY_LENGTH(mIsPropertyRunningOnCompositor),
|
|
"The length of mIsPropertyRunningOnCompositor should equal to"
|
|
"the length of LayserAnimationInfo::sRecords");
|
|
MOZ_ASSERT(nsCSSProps::PropHasFlags(aProperty,
|
|
CSS_PROPERTY_CAN_ANIMATE_ON_COMPOSITOR),
|
|
"Property being animated on compositor is a recognized "
|
|
"compositor-animatable property");
|
|
const auto& info = LayerAnimationInfo::sRecords;
|
|
for (size_t i = 0; i < ArrayLength(mIsPropertyRunningOnCompositor); i++) {
|
|
if (info[i].mProperty == aProperty) {
|
|
mIsPropertyRunningOnCompositor[i] = aIsRunning;
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
// We need to define this here since Animation is an incomplete type
|
|
// (forward-declared) in the header.
|
|
KeyframeEffectReadOnly::~KeyframeEffectReadOnly()
|
|
{
|
|
}
|
|
|
|
void
|
|
KeyframeEffectReadOnly::ResetIsRunningOnCompositor()
|
|
{
|
|
for (bool& isPropertyRunningOnCompositor : mIsPropertyRunningOnCompositor) {
|
|
isPropertyRunningOnCompositor = false;
|
|
}
|
|
}
|
|
|
|
#ifdef DEBUG
|
|
void
|
|
DumpAnimationProperties(nsTArray<AnimationProperty>& aAnimationProperties)
|
|
{
|
|
for (auto& p : aAnimationProperties) {
|
|
printf("%s\n", nsCSSProps::GetStringValue(p.mProperty).get());
|
|
for (auto& s : p.mSegments) {
|
|
nsString fromValue, toValue;
|
|
StyleAnimationValue::UncomputeValue(p.mProperty,
|
|
s.mFromValue,
|
|
fromValue);
|
|
StyleAnimationValue::UncomputeValue(p.mProperty,
|
|
s.mToValue,
|
|
toValue);
|
|
printf(" %f..%f: %s..%s\n", s.mFromKey, s.mToKey,
|
|
NS_ConvertUTF16toUTF8(fromValue).get(),
|
|
NS_ConvertUTF16toUTF8(toValue).get());
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
|
|
// Extract an iteration duration from an UnrestrictedDoubleOrXXX object.
|
|
template <typename T>
|
|
static TimeDuration
|
|
GetIterationDuration(const T& aDuration) {
|
|
// Always return the same object to benefit from return-value optimization.
|
|
TimeDuration result;
|
|
if (aDuration.IsUnrestrictedDouble()) {
|
|
double durationMs = aDuration.GetAsUnrestrictedDouble();
|
|
if (!IsNaN(durationMs) && durationMs >= 0.0f) {
|
|
result = TimeDuration::FromMilliseconds(durationMs);
|
|
}
|
|
}
|
|
// else, aDuration should be zero
|
|
return result;
|
|
}
|
|
|
|
/* static */ AnimationTiming
|
|
KeyframeEffectReadOnly::ConvertKeyframeEffectOptions(
|
|
const UnrestrictedDoubleOrKeyframeEffectOptions& aOptions)
|
|
{
|
|
AnimationTiming animationTiming;
|
|
|
|
if (aOptions.IsKeyframeEffectOptions()) {
|
|
const KeyframeEffectOptions& opt = aOptions.GetAsKeyframeEffectOptions();
|
|
|
|
animationTiming.mIterationDuration = GetIterationDuration(opt.mDuration);
|
|
animationTiming.mDelay = TimeDuration::FromMilliseconds(opt.mDelay);
|
|
// FIXME: Covert mIterationCount to a valid value.
|
|
// Bug 1214536 should revise this and keep the original value, so
|
|
// AnimationTimingEffectReadOnly can get the original iterations.
|
|
animationTiming.mIterationCount = (IsNaN(opt.mIterations) ||
|
|
opt.mIterations < 0.0f) ?
|
|
1.0f :
|
|
opt.mIterations;
|
|
animationTiming.mDirection = opt.mDirection;
|
|
// FIXME: We should store original value.
|
|
animationTiming.mFillMode = (opt.mFill == FillMode::Auto) ?
|
|
FillMode::None :
|
|
opt.mFill;
|
|
} else {
|
|
animationTiming.mIterationDuration = GetIterationDuration(aOptions);
|
|
animationTiming.mDelay = TimeDuration(0);
|
|
animationTiming.mIterationCount = 1.0f;
|
|
animationTiming.mDirection = PlaybackDirection::Normal;
|
|
animationTiming.mFillMode = FillMode::None;
|
|
}
|
|
return animationTiming;
|
|
}
|
|
|
|
/**
|
|
* A property and StyleAnimationValue pair.
|
|
*/
|
|
struct KeyframeValue
|
|
{
|
|
nsCSSProperty mProperty;
|
|
StyleAnimationValue mValue;
|
|
};
|
|
|
|
/**
|
|
* Represents a relative position for a value in a keyframe animation.
|
|
*/
|
|
enum class ValuePosition
|
|
{
|
|
First, // value at 0 used for reverse filling
|
|
Left, // value coming in to a given offset
|
|
Right, // value coming out from a given offset
|
|
Last // value at 1 used for forward filling
|
|
};
|
|
|
|
/**
|
|
* A single value in a keyframe animation, used by GetFrames to produce a
|
|
* minimal set of Keyframe objects.
|
|
*/
|
|
struct OrderedKeyframeValueEntry : KeyframeValue
|
|
{
|
|
float mOffset;
|
|
const ComputedTimingFunction* mTimingFunction;
|
|
ValuePosition mPosition;
|
|
|
|
bool SameKeyframe(const OrderedKeyframeValueEntry& aOther) const
|
|
{
|
|
return mOffset == aOther.mOffset &&
|
|
!!mTimingFunction == !!aOther.mTimingFunction &&
|
|
(!mTimingFunction || *mTimingFunction == *aOther.mTimingFunction) &&
|
|
mPosition == aOther.mPosition;
|
|
}
|
|
|
|
struct ForKeyframeGenerationComparator
|
|
{
|
|
static bool Equals(const OrderedKeyframeValueEntry& aLhs,
|
|
const OrderedKeyframeValueEntry& aRhs)
|
|
{
|
|
return aLhs.SameKeyframe(aRhs) &&
|
|
aLhs.mProperty == aRhs.mProperty;
|
|
}
|
|
static bool LessThan(const OrderedKeyframeValueEntry& aLhs,
|
|
const OrderedKeyframeValueEntry& aRhs)
|
|
{
|
|
// First, sort by offset.
|
|
if (aLhs.mOffset != aRhs.mOffset) {
|
|
return aLhs.mOffset < aRhs.mOffset;
|
|
}
|
|
|
|
// Second, by position.
|
|
if (aLhs.mPosition != aRhs.mPosition) {
|
|
return aLhs.mPosition < aRhs.mPosition;
|
|
}
|
|
|
|
// Third, by easing.
|
|
if (aLhs.mTimingFunction) {
|
|
if (aRhs.mTimingFunction) {
|
|
int32_t order = aLhs.mTimingFunction->Compare(*aRhs.mTimingFunction);
|
|
if (order != 0) {
|
|
return order < 0;
|
|
}
|
|
} else {
|
|
return true;
|
|
}
|
|
} else {
|
|
if (aRhs.mTimingFunction) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// Last, by property IDL name.
|
|
return nsCSSProps::PropertyIDLNameSortPosition(aLhs.mProperty) <
|
|
nsCSSProps::PropertyIDLNameSortPosition(aRhs.mProperty);
|
|
}
|
|
};
|
|
};
|
|
|
|
/**
|
|
* Data for a segment in a keyframe animation of a given property
|
|
* whose value is a StyleAnimationValue.
|
|
*
|
|
* KeyframeValueEntry is used in BuildAnimationPropertyListFromKeyframeSequence
|
|
* to gather data for each individual segment described by an author-supplied
|
|
* an IDL sequence<Keyframe> value so that they can be parsed into mProperties.
|
|
*/
|
|
struct KeyframeValueEntry : KeyframeValue
|
|
{
|
|
float mOffset;
|
|
ComputedTimingFunction mTimingFunction;
|
|
|
|
struct PropertyOffsetComparator
|
|
{
|
|
static bool Equals(const KeyframeValueEntry& aLhs,
|
|
const KeyframeValueEntry& aRhs)
|
|
{
|
|
return aLhs.mProperty == aRhs.mProperty &&
|
|
aLhs.mOffset == aRhs.mOffset;
|
|
}
|
|
static bool LessThan(const KeyframeValueEntry& aLhs,
|
|
const KeyframeValueEntry& aRhs)
|
|
{
|
|
// First, sort by property IDL name.
|
|
int32_t order = nsCSSProps::PropertyIDLNameSortPosition(aLhs.mProperty) -
|
|
nsCSSProps::PropertyIDLNameSortPosition(aRhs.mProperty);
|
|
if (order != 0) {
|
|
return order < 0;
|
|
}
|
|
|
|
// Then, by offset.
|
|
return aLhs.mOffset < aRhs.mOffset;
|
|
}
|
|
};
|
|
};
|
|
|
|
/**
|
|
* A property-values pair obtained from the open-ended properties
|
|
* discovered on a Keyframe or PropertyIndexedKeyframes object.
|
|
*
|
|
* Single values (as required by Keyframe, and as also supported
|
|
* on PropertyIndexedKeyframes) are stored as the only element in
|
|
* mValues.
|
|
*/
|
|
struct PropertyValuesPair
|
|
{
|
|
nsCSSProperty mProperty;
|
|
nsTArray<nsString> mValues;
|
|
|
|
class PropertyPriorityComparator
|
|
{
|
|
public:
|
|
PropertyPriorityComparator()
|
|
: mSubpropertyCountInitialized(false) {}
|
|
|
|
bool Equals(const PropertyValuesPair& aLhs,
|
|
const PropertyValuesPair& aRhs) const
|
|
{
|
|
return aLhs.mProperty == aRhs.mProperty;
|
|
}
|
|
|
|
bool LessThan(const PropertyValuesPair& aLhs,
|
|
const PropertyValuesPair& aRhs) const
|
|
{
|
|
bool isShorthandLhs = nsCSSProps::IsShorthand(aLhs.mProperty);
|
|
bool isShorthandRhs = nsCSSProps::IsShorthand(aRhs.mProperty);
|
|
|
|
if (isShorthandLhs) {
|
|
if (isShorthandRhs) {
|
|
// First, sort shorthands by the number of longhands they have.
|
|
uint32_t subpropCountLhs = SubpropertyCount(aLhs.mProperty);
|
|
uint32_t subpropCountRhs = SubpropertyCount(aRhs.mProperty);
|
|
if (subpropCountLhs != subpropCountRhs) {
|
|
return subpropCountLhs < subpropCountRhs;
|
|
}
|
|
// Otherwise, sort by IDL name below.
|
|
} else {
|
|
// Put longhands before shorthands.
|
|
return false;
|
|
}
|
|
} else {
|
|
if (isShorthandRhs) {
|
|
// Put longhands before shorthands.
|
|
return true;
|
|
}
|
|
}
|
|
// For two longhand properties, or two shorthand with the same number
|
|
// of longhand components, sort by IDL name.
|
|
return nsCSSProps::PropertyIDLNameSortPosition(aLhs.mProperty) <
|
|
nsCSSProps::PropertyIDLNameSortPosition(aRhs.mProperty);
|
|
}
|
|
|
|
uint32_t SubpropertyCount(nsCSSProperty aProperty) const
|
|
{
|
|
if (!mSubpropertyCountInitialized) {
|
|
PodZero(&mSubpropertyCount);
|
|
mSubpropertyCountInitialized = true;
|
|
}
|
|
if (mSubpropertyCount[aProperty] == 0) {
|
|
uint32_t count = 0;
|
|
CSSPROPS_FOR_SHORTHAND_SUBPROPERTIES(
|
|
p, aProperty, nsCSSProps::eEnabledForAllContent) {
|
|
++count;
|
|
}
|
|
mSubpropertyCount[aProperty] = count;
|
|
}
|
|
return mSubpropertyCount[aProperty];
|
|
}
|
|
|
|
private:
|
|
// Cache of shorthand subproperty counts.
|
|
mutable RangedArray<
|
|
uint32_t,
|
|
eCSSProperty_COUNT_no_shorthands,
|
|
eCSSProperty_COUNT - eCSSProperty_COUNT_no_shorthands> mSubpropertyCount;
|
|
mutable bool mSubpropertyCountInitialized;
|
|
};
|
|
};
|
|
|
|
/**
|
|
* The result of parsing a JS object as a Keyframe dictionary
|
|
* and getting its property-value pairs from its open-ended
|
|
* properties.
|
|
*/
|
|
struct OffsetIndexedKeyframe
|
|
{
|
|
binding_detail::FastKeyframe mKeyframeDict;
|
|
nsTArray<PropertyValuesPair> mPropertyValuePairs;
|
|
};
|
|
|
|
/**
|
|
* Parses a CSS <single-transition-timing-function> value from
|
|
* aEasing into a ComputedTimingFunction. If parsing fails, aResult will
|
|
* be set to 'linear'.
|
|
*/
|
|
static void
|
|
ParseEasing(Element* aTarget,
|
|
const nsAString& aEasing,
|
|
ComputedTimingFunction& aResult)
|
|
{
|
|
nsIDocument* doc = aTarget->OwnerDoc();
|
|
|
|
nsCSSValue value;
|
|
nsCSSParser parser;
|
|
parser.ParseLonghandProperty(eCSSProperty_animation_timing_function,
|
|
aEasing,
|
|
doc->GetDocumentURI(),
|
|
doc->GetDocumentURI(),
|
|
doc->NodePrincipal(),
|
|
value);
|
|
|
|
switch (value.GetUnit()) {
|
|
case eCSSUnit_List: {
|
|
const nsCSSValueList* list = value.GetListValue();
|
|
if (list->mNext) {
|
|
// don't support a list of timing functions
|
|
break;
|
|
}
|
|
switch (list->mValue.GetUnit()) {
|
|
case eCSSUnit_Enumerated:
|
|
case eCSSUnit_Cubic_Bezier:
|
|
case eCSSUnit_Steps: {
|
|
nsTimingFunction timingFunction;
|
|
nsRuleNode::ComputeTimingFunction(list->mValue, timingFunction);
|
|
aResult.Init(timingFunction);
|
|
return;
|
|
}
|
|
default:
|
|
MOZ_ASSERT_UNREACHABLE("unexpected animation-timing-function list "
|
|
"item unit");
|
|
break;
|
|
}
|
|
break;
|
|
}
|
|
case eCSSUnit_Null:
|
|
case eCSSUnit_Inherit:
|
|
case eCSSUnit_Initial:
|
|
case eCSSUnit_Unset:
|
|
case eCSSUnit_TokenStream:
|
|
break;
|
|
default:
|
|
MOZ_ASSERT_UNREACHABLE("unexpected animation-timing-function unit");
|
|
break;
|
|
}
|
|
|
|
aResult.Init(nsTimingFunction(NS_STYLE_TRANSITION_TIMING_FUNCTION_LINEAR));
|
|
}
|
|
|
|
/**
|
|
* An additional property (for a property-values pair) found on a Keyframe
|
|
* or PropertyIndexedKeyframes object.
|
|
*/
|
|
struct AdditionalProperty
|
|
{
|
|
nsCSSProperty mProperty;
|
|
size_t mJsidIndex; // Index into |ids| in GetPropertyValuesPairs.
|
|
|
|
struct PropertyComparator
|
|
{
|
|
bool Equals(const AdditionalProperty& aLhs,
|
|
const AdditionalProperty& aRhs) const
|
|
{
|
|
return aLhs.mProperty == aRhs.mProperty;
|
|
}
|
|
bool LessThan(const AdditionalProperty& aLhs,
|
|
const AdditionalProperty& aRhs) const
|
|
{
|
|
return nsCSSProps::PropertyIDLNameSortPosition(aLhs.mProperty) <
|
|
nsCSSProps::PropertyIDLNameSortPosition(aRhs.mProperty);
|
|
}
|
|
};
|
|
};
|
|
|
|
/**
|
|
* Converts aValue to DOMString and appends it to aValues.
|
|
*/
|
|
static bool
|
|
AppendValueAsString(JSContext* aCx,
|
|
nsTArray<nsString>& aValues,
|
|
JS::Handle<JS::Value> aValue)
|
|
{
|
|
return ConvertJSValueToString(aCx, aValue, eStringify, eStringify,
|
|
*aValues.AppendElement());
|
|
}
|
|
|
|
// For the aAllowList parameter of AppendStringOrStringSequence and
|
|
// GetPropertyValuesPairs.
|
|
enum class ListAllowance { eDisallow, eAllow };
|
|
|
|
/**
|
|
* Converts aValue to DOMString, if aAllowLists is eDisallow, or
|
|
* to (DOMString or sequence<DOMString>) if aAllowLists is aAllow.
|
|
* The resulting strings are appended to aValues.
|
|
*/
|
|
static bool
|
|
AppendStringOrStringSequenceToArray(JSContext* aCx,
|
|
JS::Handle<JS::Value> aValue,
|
|
ListAllowance aAllowLists,
|
|
nsTArray<nsString>& aValues)
|
|
{
|
|
if (aAllowLists == ListAllowance::eAllow && aValue.isObject()) {
|
|
// The value is an object, and we want to allow lists; convert
|
|
// aValue to (DOMString or sequence<DOMString>).
|
|
JS::ForOfIterator iter(aCx);
|
|
if (!iter.init(aValue, JS::ForOfIterator::AllowNonIterable)) {
|
|
return false;
|
|
}
|
|
if (iter.valueIsIterable()) {
|
|
// If the object is iterable, convert it to sequence<DOMString>.
|
|
JS::Rooted<JS::Value> element(aCx);
|
|
for (;;) {
|
|
bool done;
|
|
if (!iter.next(&element, &done)) {
|
|
return false;
|
|
}
|
|
if (done) {
|
|
break;
|
|
}
|
|
if (!AppendValueAsString(aCx, aValues, element)) {
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
}
|
|
|
|
// Either the object is not iterable, or aAllowLists doesn't want
|
|
// a list; convert it to DOMString.
|
|
if (!AppendValueAsString(aCx, aValues, aValue)) {
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Reads the property-values pairs from the specified JS object.
|
|
*
|
|
* @param aObject The JS object to look at.
|
|
* @param aAllowLists If eAllow, values will be converted to
|
|
* (DOMString or sequence<DOMString); if eDisallow, values
|
|
* will be converted to DOMString.
|
|
* @param aResult The array into which the enumerated property-values
|
|
* pairs will be stored.
|
|
* @return false on failure or JS exception thrown while interacting
|
|
* with aObject; true otherwise.
|
|
*/
|
|
static bool
|
|
GetPropertyValuesPairs(JSContext* aCx,
|
|
JS::Handle<JSObject*> aObject,
|
|
ListAllowance aAllowLists,
|
|
nsTArray<PropertyValuesPair>& aResult)
|
|
{
|
|
nsTArray<AdditionalProperty> properties;
|
|
|
|
// Iterate over all the properties on aObject and append an
|
|
// entry to properties for them.
|
|
//
|
|
// We don't compare the jsids that we encounter with those for
|
|
// the explicit dictionary members, since we know that none
|
|
// of the CSS property IDL names clash with them.
|
|
JS::Rooted<JS::IdVector> ids(aCx, JS::IdVector(aCx));
|
|
if (!JS_Enumerate(aCx, aObject, &ids)) {
|
|
return false;
|
|
}
|
|
for (size_t i = 0, n = ids.length(); i < n; i++) {
|
|
nsAutoJSString propName;
|
|
if (!propName.init(aCx, ids[i])) {
|
|
return false;
|
|
}
|
|
nsCSSProperty property =
|
|
nsCSSProps::LookupPropertyByIDLName(propName,
|
|
nsCSSProps::eEnabledForAllContent);
|
|
if (property != eCSSProperty_UNKNOWN &&
|
|
nsCSSProps::kAnimTypeTable[property] != eStyleAnimType_None) {
|
|
AdditionalProperty* p = properties.AppendElement();
|
|
p->mProperty = property;
|
|
p->mJsidIndex = i;
|
|
}
|
|
}
|
|
|
|
// Sort the entries by IDL name and then get each value and
|
|
// convert it either to a DOMString or to a
|
|
// (DOMString or sequence<DOMString>), depending on aAllowLists,
|
|
// and build up aResult.
|
|
properties.Sort(AdditionalProperty::PropertyComparator());
|
|
|
|
for (AdditionalProperty& p : properties) {
|
|
JS::Rooted<JS::Value> value(aCx);
|
|
if (!JS_GetPropertyById(aCx, aObject, ids[p.mJsidIndex], &value)) {
|
|
return false;
|
|
}
|
|
PropertyValuesPair* pair = aResult.AppendElement();
|
|
pair->mProperty = p.mProperty;
|
|
if (!AppendStringOrStringSequenceToArray(aCx, value, aAllowLists,
|
|
pair->mValues)) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Converts a JS object wrapped by the given JS::ForIfIterator to an
|
|
* IDL sequence<Keyframe> and stores the resulting OffsetIndexedKeyframe
|
|
* objects in aResult.
|
|
*/
|
|
static bool
|
|
ConvertKeyframeSequence(JSContext* aCx,
|
|
JS::ForOfIterator& aIterator,
|
|
nsTArray<OffsetIndexedKeyframe>& aResult)
|
|
{
|
|
JS::Rooted<JS::Value> value(aCx);
|
|
for (;;) {
|
|
bool done;
|
|
if (!aIterator.next(&value, &done)) {
|
|
return false;
|
|
}
|
|
if (done) {
|
|
break;
|
|
}
|
|
// Each value found when iterating the object must be an object
|
|
// or null/undefined (which gets treated as a default {} dictionary
|
|
// value).
|
|
if (!value.isObject() && !value.isNullOrUndefined()) {
|
|
ThrowErrorMessage(aCx, MSG_NOT_OBJECT,
|
|
"Element of sequence<Keyframes> argument");
|
|
return false;
|
|
}
|
|
// Convert the JS value into a Keyframe dictionary value.
|
|
OffsetIndexedKeyframe* keyframe = aResult.AppendElement();
|
|
if (!keyframe->mKeyframeDict.Init(
|
|
aCx, value, "Element of sequence<Keyframes> argument")) {
|
|
return false;
|
|
}
|
|
// Look for additional property-values pairs on the object.
|
|
if (value.isObject()) {
|
|
JS::Rooted<JSObject*> object(aCx, &value.toObject());
|
|
if (!GetPropertyValuesPairs(aCx, object,
|
|
ListAllowance::eDisallow,
|
|
keyframe->mPropertyValuePairs)) {
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Checks that the given keyframes are loosely ordered (each keyframe's
|
|
* offset that is not null is greater than or equal to the previous
|
|
* non-null offset) and that all values are within the range [0.0, 1.0].
|
|
*
|
|
* @return true if the keyframes' offsets are correctly ordered and
|
|
* within range; false otherwise.
|
|
*/
|
|
static bool
|
|
HasValidOffsets(const nsTArray<OffsetIndexedKeyframe>& aKeyframes)
|
|
{
|
|
double offset = 0.0;
|
|
for (const OffsetIndexedKeyframe& keyframe : aKeyframes) {
|
|
if (!keyframe.mKeyframeDict.mOffset.IsNull()) {
|
|
double thisOffset = keyframe.mKeyframeDict.mOffset.Value();
|
|
if (thisOffset < offset || thisOffset > 1.0f) {
|
|
return false;
|
|
}
|
|
offset = thisOffset;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Fills in any null offsets for the given keyframes by applying the
|
|
* "distribute" spacing algorithm.
|
|
*
|
|
* http://w3c.github.io/web-animations/#distribute-keyframe-spacing-mode
|
|
*/
|
|
static void
|
|
ApplyDistributeSpacing(nsTArray<OffsetIndexedKeyframe>& aKeyframes)
|
|
{
|
|
// If the first or last keyframes have an unspecified offset,
|
|
// fill them in with 0% and 100%. If there is only a single keyframe,
|
|
// then it gets 100%.
|
|
if (aKeyframes.LastElement().mKeyframeDict.mOffset.IsNull()) {
|
|
aKeyframes.LastElement().mKeyframeDict.mOffset.SetValue(1.0);
|
|
}
|
|
if (aKeyframes[0].mKeyframeDict.mOffset.IsNull()) {
|
|
aKeyframes[0].mKeyframeDict.mOffset.SetValue(0.0);
|
|
}
|
|
|
|
// Fill in remaining missing offsets.
|
|
size_t i = 0;
|
|
while (i < aKeyframes.Length() - 1) {
|
|
MOZ_ASSERT(!aKeyframes[i].mKeyframeDict.mOffset.IsNull());
|
|
double start = aKeyframes[i].mKeyframeDict.mOffset.Value();
|
|
size_t j = i + 1;
|
|
while (aKeyframes[j].mKeyframeDict.mOffset.IsNull()) {
|
|
++j;
|
|
}
|
|
double end = aKeyframes[j].mKeyframeDict.mOffset.Value();
|
|
size_t n = j - i;
|
|
for (size_t k = 1; k < n; ++k) {
|
|
double offset = start + double(k) / n * (end - start);
|
|
aKeyframes[i + k].mKeyframeDict.mOffset.SetValue(offset);
|
|
}
|
|
i = j;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Splits out each property's keyframe animation segment information
|
|
* from the OffsetIndexedKeyframe objects into an array of KeyframeValueEntry.
|
|
*
|
|
* The easing string value in OffsetIndexedKeyframe objects is parsed
|
|
* into a ComputedTimingFunction value in the corresponding KeyframeValueEntry
|
|
* objects.
|
|
*
|
|
* @param aTarget The target of the animation.
|
|
* @param aKeyframes The keyframes to read.
|
|
* @param aResult The array to append the resulting KeyframeValueEntry
|
|
* objects to.
|
|
*/
|
|
static void
|
|
GenerateValueEntries(Element* aTarget,
|
|
nsTArray<OffsetIndexedKeyframe>& aKeyframes,
|
|
nsTArray<KeyframeValueEntry>& aResult,
|
|
ErrorResult& aRv)
|
|
{
|
|
nsCSSPropertySet properties; // All properties encountered.
|
|
nsCSSPropertySet propertiesWithFromValue; // Those with a defined 0% value.
|
|
nsCSSPropertySet propertiesWithToValue; // Those with a defined 100% value.
|
|
|
|
for (OffsetIndexedKeyframe& keyframe : aKeyframes) {
|
|
float offset = float(keyframe.mKeyframeDict.mOffset.Value());
|
|
ComputedTimingFunction easing;
|
|
ParseEasing(aTarget, keyframe.mKeyframeDict.mEasing, easing);
|
|
// We ignore keyframe.mKeyframeDict.mComposite since we don't support
|
|
// composite modes on keyframes yet.
|
|
|
|
// keyframe.mPropertyValuePairs is currently sorted by CSS property IDL
|
|
// name, since that was the order we read the properties from the JS
|
|
// object. Re-sort the list so that longhand properties appear before
|
|
// shorthands, and with shorthands all appearing in increasing order of
|
|
// number of components. For two longhand properties, or two shorthands
|
|
// with the same number of components, sort by IDL name.
|
|
//
|
|
// Example orderings that result from this:
|
|
//
|
|
// margin-left, margin
|
|
//
|
|
// and:
|
|
//
|
|
// border-top-color, border-color, border-top, border
|
|
//
|
|
// This allows us to prioritize values specified by longhands (or smaller
|
|
// shorthand subsets) when longhands and shorthands are both specified
|
|
// on the one keyframe.
|
|
keyframe.mPropertyValuePairs.Sort(
|
|
PropertyValuesPair::PropertyPriorityComparator());
|
|
|
|
nsCSSPropertySet propertiesOnThisKeyframe;
|
|
for (const PropertyValuesPair& pair : keyframe.mPropertyValuePairs) {
|
|
MOZ_ASSERT(pair.mValues.Length() == 1,
|
|
"ConvertKeyframeSequence should have parsed single "
|
|
"DOMString values from the property-values pairs");
|
|
// Parse the property's string value and produce a KeyframeValueEntry (or
|
|
// more than one, for shorthands) for it.
|
|
nsTArray<PropertyStyleAnimationValuePair> values;
|
|
if (StyleAnimationValue::ComputeValues(pair.mProperty,
|
|
nsCSSProps::eEnabledForAllContent,
|
|
aTarget,
|
|
pair.mValues[0],
|
|
/* aUseSVGMode */ false,
|
|
values)) {
|
|
for (auto& value : values) {
|
|
// If we already got a value for this property on the keyframe,
|
|
// skip this one.
|
|
if (propertiesOnThisKeyframe.HasProperty(value.mProperty)) {
|
|
continue;
|
|
}
|
|
|
|
KeyframeValueEntry* entry = aResult.AppendElement();
|
|
entry->mOffset = offset;
|
|
entry->mProperty = value.mProperty;
|
|
entry->mValue = value.mValue;
|
|
entry->mTimingFunction = easing;
|
|
|
|
if (offset == 0.0) {
|
|
propertiesWithFromValue.AddProperty(value.mProperty);
|
|
} else if (offset == 1.0) {
|
|
propertiesWithToValue.AddProperty(value.mProperty);
|
|
}
|
|
propertiesOnThisKeyframe.AddProperty(value.mProperty);
|
|
properties.AddProperty(value.mProperty);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// We don't support additive segments and so can't support missing properties
|
|
// using their underlying value in 0% and 100% keyframes. Throw an exception
|
|
// until we do support this.
|
|
if (!propertiesWithFromValue.Equals(properties) ||
|
|
!propertiesWithToValue.Equals(properties)) {
|
|
aRv.Throw(NS_ERROR_DOM_ANIM_MISSING_PROPS_ERR);
|
|
return;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Builds an array of AnimationProperty objects to represent the keyframe
|
|
* animation segments in aEntries.
|
|
*/
|
|
static void
|
|
BuildSegmentsFromValueEntries(nsTArray<KeyframeValueEntry>& aEntries,
|
|
nsTArray<AnimationProperty>& aResult)
|
|
{
|
|
if (aEntries.IsEmpty()) {
|
|
return;
|
|
}
|
|
|
|
// Sort the KeyframeValueEntry objects so that all entries for a given
|
|
// property are together, and the entries are sorted by offset otherwise.
|
|
std::stable_sort(aEntries.begin(), aEntries.end(),
|
|
&KeyframeValueEntry::PropertyOffsetComparator::LessThan);
|
|
|
|
MOZ_ASSERT(aEntries[0].mOffset == 0.0f);
|
|
MOZ_ASSERT(aEntries.LastElement().mOffset == 1.0f);
|
|
|
|
// For a given index i, we want to generate a segment from aEntries[i]
|
|
// to aEntries[j], if:
|
|
//
|
|
// * j > i,
|
|
// * aEntries[i + 1]'s offset/property is different from aEntries[i]'s, and
|
|
// * aEntries[j - 1]'s offset/property is different from aEntries[j]'s.
|
|
//
|
|
// That will eliminate runs of same offset/property values where there's no
|
|
// point generating zero length segments in the middle of the animation.
|
|
//
|
|
// Additionally we need to generate a zero length segment at offset 0 and at
|
|
// offset 1, if we have multiple values for a given property at that offset,
|
|
// since we need to retain the very first and very last value so they can
|
|
// be used for reverse and forward filling.
|
|
|
|
nsCSSProperty lastProperty = eCSSProperty_UNKNOWN;
|
|
AnimationProperty* animationProperty = nullptr;
|
|
|
|
size_t i = 0, n = aEntries.Length();
|
|
|
|
while (i + 1 < n) {
|
|
// Starting from i, determine the next [i, j] interval from which to
|
|
// generate a segment.
|
|
size_t j;
|
|
if (aEntries[i].mOffset == 0.0f && aEntries[i + 1].mOffset == 0.0f) {
|
|
// We need to generate an initial zero-length segment.
|
|
MOZ_ASSERT(aEntries[i].mProperty == aEntries[i + 1].mProperty);
|
|
j = i + 1;
|
|
while (aEntries[j + 1].mOffset == 0.0f) {
|
|
MOZ_ASSERT(aEntries[j].mProperty == aEntries[j + 1].mProperty);
|
|
++j;
|
|
}
|
|
} else if (aEntries[i].mOffset == 1.0f) {
|
|
if (aEntries[i + 1].mOffset == 1.0f) {
|
|
// We need to generate a final zero-length segment.
|
|
MOZ_ASSERT(aEntries[i].mProperty == aEntries[i].mProperty);
|
|
j = i + 1;
|
|
while (j + 1 < n && aEntries[j + 1].mOffset == 1.0f) {
|
|
MOZ_ASSERT(aEntries[j].mProperty == aEntries[j + 1].mProperty);
|
|
++j;
|
|
}
|
|
} else {
|
|
// New property.
|
|
MOZ_ASSERT(aEntries[i + 1].mOffset == 0.0f);
|
|
MOZ_ASSERT(aEntries[i].mProperty != aEntries[i + 1].mProperty);
|
|
++i;
|
|
continue;
|
|
}
|
|
} else {
|
|
while (aEntries[i].mOffset == aEntries[i + 1].mOffset &&
|
|
aEntries[i].mProperty == aEntries[i + 1].mProperty) {
|
|
++i;
|
|
}
|
|
j = i + 1;
|
|
}
|
|
|
|
// If we've moved on to a new property, create a new AnimationProperty
|
|
// to insert segments into.
|
|
if (aEntries[i].mProperty != lastProperty) {
|
|
MOZ_ASSERT(aEntries[i].mOffset == 0.0f);
|
|
animationProperty = aResult.AppendElement();
|
|
animationProperty->mProperty = aEntries[i].mProperty;
|
|
animationProperty->mWinsInCascade = true;
|
|
lastProperty = aEntries[i].mProperty;
|
|
}
|
|
|
|
// Now generate the segment.
|
|
AnimationPropertySegment* segment =
|
|
animationProperty->mSegments.AppendElement();
|
|
segment->mFromKey = aEntries[i].mOffset;
|
|
segment->mToKey = aEntries[j].mOffset;
|
|
segment->mFromValue = aEntries[i].mValue;
|
|
segment->mToValue = aEntries[j].mValue;
|
|
segment->mTimingFunction = aEntries[i].mTimingFunction;
|
|
|
|
i = j;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Converts a JS object to an IDL sequence<Keyframe> and builds an
|
|
* array of AnimationProperty objects for the keyframe animation
|
|
* that it specifies.
|
|
*
|
|
* @param aTarget The target of the animation.
|
|
* @param aIterator An already-initialized ForOfIterator for the JS
|
|
* object to iterate over as a sequence.
|
|
* @param aResult The array into which the resulting AnimationProperty
|
|
* objects will be appended.
|
|
*/
|
|
static void
|
|
BuildAnimationPropertyListFromKeyframeSequence(
|
|
JSContext* aCx,
|
|
Element* aTarget,
|
|
JS::ForOfIterator& aIterator,
|
|
nsTArray<AnimationProperty>& aResult,
|
|
ErrorResult& aRv)
|
|
{
|
|
// Convert the object in aIterator to sequence<Keyframe>, producing
|
|
// an array of OffsetIndexedKeyframe objects.
|
|
nsAutoTArray<OffsetIndexedKeyframe,4> keyframes;
|
|
if (!ConvertKeyframeSequence(aCx, aIterator, keyframes)) {
|
|
aRv.Throw(NS_ERROR_FAILURE);
|
|
return;
|
|
}
|
|
|
|
// If the sequence<> had zero elements, we won't generate any
|
|
// keyframes.
|
|
if (keyframes.IsEmpty()) {
|
|
return;
|
|
}
|
|
|
|
// Check that the keyframes are loosely sorted and with values all
|
|
// between 0% and 100%.
|
|
if (!HasValidOffsets(keyframes)) {
|
|
aRv.ThrowTypeError<MSG_INVALID_KEYFRAME_OFFSETS>();
|
|
return;
|
|
}
|
|
|
|
// Fill in 0%/100% values if the first/element keyframes don't have
|
|
// a specified offset, and evenly space those that have a missing
|
|
// offset. (We don't support paced spacing yet.)
|
|
ApplyDistributeSpacing(keyframes);
|
|
|
|
// Convert the OffsetIndexedKeyframes into a list of KeyframeValueEntry
|
|
// objects.
|
|
nsTArray<KeyframeValueEntry> entries;
|
|
GenerateValueEntries(aTarget, keyframes, entries, aRv);
|
|
if (aRv.Failed()) {
|
|
return;
|
|
}
|
|
|
|
// Finally, build an array of AnimationProperty objects in aResult
|
|
// corresponding to the entries.
|
|
BuildSegmentsFromValueEntries(entries, aResult);
|
|
}
|
|
|
|
/**
|
|
* Converts a JS object to an IDL PropertyIndexedKeyframes and builds an
|
|
* array of AnimationProperty objects for the keyframe animation
|
|
* that it specifies.
|
|
*
|
|
* @param aTarget The target of the animation.
|
|
* @param aValue The JS object.
|
|
* @param aResult The array into which the resulting AnimationProperty
|
|
* objects will be appended.
|
|
*/
|
|
static void
|
|
BuildAnimationPropertyListFromPropertyIndexedKeyframes(
|
|
JSContext* aCx,
|
|
Element* aTarget,
|
|
JS::Handle<JS::Value> aValue,
|
|
InfallibleTArray<AnimationProperty>& aResult,
|
|
ErrorResult& aRv)
|
|
{
|
|
MOZ_ASSERT(aValue.isObject());
|
|
|
|
// Convert the object to a PropertyIndexedKeyframes dictionary to
|
|
// get its explicit dictionary members.
|
|
binding_detail::FastPropertyIndexedKeyframes keyframes;
|
|
if (!keyframes.Init(aCx, aValue, "PropertyIndexedKeyframes argument",
|
|
false)) {
|
|
aRv.Throw(NS_ERROR_FAILURE);
|
|
return;
|
|
}
|
|
|
|
ComputedTimingFunction easing;
|
|
ParseEasing(aTarget, keyframes.mEasing, easing);
|
|
|
|
// We ignore easing.mComposite since we don't support composite modes on
|
|
// keyframes yet.
|
|
|
|
// Get all the property--value-list pairs off the object.
|
|
JS::Rooted<JSObject*> object(aCx, &aValue.toObject());
|
|
nsTArray<PropertyValuesPair> propertyValuesPairs;
|
|
if (!GetPropertyValuesPairs(aCx, object, ListAllowance::eAllow,
|
|
propertyValuesPairs)) {
|
|
aRv.Throw(NS_ERROR_FAILURE);
|
|
return;
|
|
}
|
|
|
|
// We must keep track of which properties we've already generated
|
|
// an AnimationProperty since the author could have specified both a
|
|
// shorthand and one of its component longhands on the
|
|
// PropertyIndexedKeyframes.
|
|
nsCSSPropertySet properties;
|
|
|
|
// Create AnimationProperty objects for each PropertyValuesPair, applying
|
|
// the "distribute" spacing algorithm to the segments.
|
|
for (const PropertyValuesPair& pair : propertyValuesPairs) {
|
|
size_t count = pair.mValues.Length();
|
|
if (count == 0) {
|
|
// No animation values for this property.
|
|
continue;
|
|
}
|
|
if (count == 1) {
|
|
// We don't support additive segments and so can't support an
|
|
// animation that goes from the underlying value to this
|
|
// specified value. Throw an exception until we do support this.
|
|
aRv.Throw(NS_ERROR_DOM_ANIM_MISSING_PROPS_ERR);
|
|
return;
|
|
}
|
|
|
|
// If we find an invalid value, we don't create a segment for it, but
|
|
// we adjust the surrounding segments so that the timing of the segments
|
|
// is the same as if we did support it. For example, animating with
|
|
// values ["red", "green", "yellow", "invalid", "blue"] will generate
|
|
// segments with this timing:
|
|
//
|
|
// 0.00 -> 0.25 : red -> green
|
|
// 0.25 -> 0.50 : green -> yellow
|
|
// 0.50 -> 1.00 : yellow -> blue
|
|
//
|
|
// With future spec clarifications we might decide to preserve the invalid
|
|
// value on the segment and make the animation code deal with the invalid
|
|
// value instead.
|
|
nsTArray<PropertyStyleAnimationValuePair> fromValues;
|
|
float fromKey = 0.0f;
|
|
if (!StyleAnimationValue::ComputeValues(pair.mProperty,
|
|
nsCSSProps::eEnabledForAllContent,
|
|
aTarget,
|
|
pair.mValues[0],
|
|
/* aUseSVGMode */ false,
|
|
fromValues)) {
|
|
// We need to throw for an invalid first value, since that would imply an
|
|
// additive animation, which we don't support yet.
|
|
aRv.Throw(NS_ERROR_DOM_ANIM_MISSING_PROPS_ERR);
|
|
return;
|
|
}
|
|
|
|
if (fromValues.IsEmpty()) {
|
|
// All longhand components of a shorthand pair.mProperty must be disabled.
|
|
continue;
|
|
}
|
|
|
|
// Create AnimationProperty objects for each property that had a
|
|
// value computed. When pair.mProperty is a longhand, it is just
|
|
// that property. When pair.mProperty is a shorthand, we'll have
|
|
// one property per longhand component.
|
|
nsTArray<size_t> animationPropertyIndexes;
|
|
animationPropertyIndexes.SetLength(fromValues.Length());
|
|
for (size_t i = 0, n = fromValues.Length(); i < n; ++i) {
|
|
nsCSSProperty p = fromValues[i].mProperty;
|
|
bool found = false;
|
|
if (properties.HasProperty(p)) {
|
|
// We have already dealt with this property. Look up and
|
|
// overwrite the old AnimationProperty object.
|
|
for (size_t j = 0, m = aResult.Length(); j < m; ++j) {
|
|
if (aResult[j].mProperty == p) {
|
|
aResult[j].mSegments.Clear();
|
|
animationPropertyIndexes[i] = j;
|
|
found = true;
|
|
break;
|
|
}
|
|
}
|
|
MOZ_ASSERT(found, "properties is inconsistent with aResult");
|
|
}
|
|
if (!found) {
|
|
// This is the first time we've encountered this property.
|
|
animationPropertyIndexes[i] = aResult.Length();
|
|
AnimationProperty* animationProperty = aResult.AppendElement();
|
|
animationProperty->mProperty = p;
|
|
animationProperty->mWinsInCascade = true;
|
|
properties.AddProperty(p);
|
|
}
|
|
}
|
|
|
|
double portion = 1.0 / (count - 1);
|
|
for (size_t i = 0; i < count - 1; ++i) {
|
|
nsTArray<PropertyStyleAnimationValuePair> toValues;
|
|
float toKey = (i + 1) * portion;
|
|
if (!StyleAnimationValue::ComputeValues(pair.mProperty,
|
|
nsCSSProps::eEnabledForAllContent,
|
|
aTarget,
|
|
pair.mValues[i + 1],
|
|
/* aUseSVGMode */ false,
|
|
toValues)) {
|
|
if (i + 1 == count - 1) {
|
|
// We need to throw for an invalid last value, since that would
|
|
// imply an additive animation, which we don't support yet.
|
|
aRv.Throw(NS_ERROR_DOM_ANIM_MISSING_PROPS_ERR);
|
|
return;
|
|
}
|
|
// Otherwise, skip the segment.
|
|
continue;
|
|
}
|
|
MOZ_ASSERT(toValues.Length() == fromValues.Length(),
|
|
"should get the same number of properties as the last time "
|
|
"we called ComputeValues for pair.mProperty");
|
|
for (size_t j = 0, n = toValues.Length(); j < n; ++j) {
|
|
size_t index = animationPropertyIndexes[j];
|
|
AnimationPropertySegment* segment =
|
|
aResult[index].mSegments.AppendElement();
|
|
segment->mFromKey = fromKey;
|
|
segment->mFromValue = fromValues[j].mValue;
|
|
segment->mToKey = toKey;
|
|
segment->mToValue = toValues[j].mValue;
|
|
segment->mTimingFunction = easing;
|
|
}
|
|
fromValues = Move(toValues);
|
|
fromKey = toKey;
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Converts a JS value to an IDL
|
|
* (PropertyIndexedKeyframes or sequence<Keyframe>) value and builds an
|
|
* array of AnimationProperty objects for the keyframe animation
|
|
* that it specifies.
|
|
*
|
|
* @param aTarget The target of the animation, used to resolve style
|
|
* for a property's underlying value if needed.
|
|
* @param aFrames The JS value, provided as an optional IDL |object?| value,
|
|
* that is the keyframe list specification.
|
|
* @param aResult The array into which the resulting AnimationProperty
|
|
* objects will be appended.
|
|
*/
|
|
/* static */ void
|
|
KeyframeEffectReadOnly::BuildAnimationPropertyList(
|
|
JSContext* aCx,
|
|
Element* aTarget,
|
|
const Optional<JS::Handle<JSObject*>>& aFrames,
|
|
InfallibleTArray<AnimationProperty>& aResult,
|
|
ErrorResult& aRv)
|
|
{
|
|
MOZ_ASSERT(aResult.IsEmpty());
|
|
|
|
// A frame list specification in the IDL is:
|
|
//
|
|
// (PropertyIndexedKeyframes or sequence<Keyframe> or SharedKeyframeList)
|
|
//
|
|
// We don't support SharedKeyframeList yet, but we do the other two. We
|
|
// manually implement the parts of JS-to-IDL union conversion algorithm
|
|
// from the Web IDL spec, since we have to represent this an object? so
|
|
// we can look at the open-ended set of properties on a
|
|
// PropertyIndexedKeyframes or Keyframe.
|
|
|
|
if (!aFrames.WasPassed() || !aFrames.Value().get()) {
|
|
// The argument was omitted, or was explicitly null. In both cases,
|
|
// the default dictionary value for PropertyIndexedKeyframes would
|
|
// result in no keyframes.
|
|
return;
|
|
}
|
|
|
|
// At this point we know we have an object. We try to convert it to a
|
|
// sequence<Keyframe> first, and if that fails due to not being iterable,
|
|
// we try to convert it to PropertyIndexedKeyframes.
|
|
JS::Rooted<JS::Value> objectValue(aCx, JS::ObjectValue(*aFrames.Value()));
|
|
JS::ForOfIterator iter(aCx);
|
|
if (!iter.init(objectValue, JS::ForOfIterator::AllowNonIterable)) {
|
|
aRv.Throw(NS_ERROR_FAILURE);
|
|
return;
|
|
}
|
|
|
|
if (iter.valueIsIterable()) {
|
|
BuildAnimationPropertyListFromKeyframeSequence(aCx, aTarget, iter,
|
|
aResult, aRv);
|
|
} else {
|
|
BuildAnimationPropertyListFromPropertyIndexedKeyframes(aCx, aTarget,
|
|
objectValue, aResult,
|
|
aRv);
|
|
}
|
|
}
|
|
|
|
/* static */ already_AddRefed<KeyframeEffectReadOnly>
|
|
KeyframeEffectReadOnly::Constructor(
|
|
const GlobalObject& aGlobal,
|
|
Element* aTarget,
|
|
const Optional<JS::Handle<JSObject*>>& aFrames,
|
|
const UnrestrictedDoubleOrKeyframeEffectOptions& aOptions,
|
|
ErrorResult& aRv)
|
|
{
|
|
if (!aTarget) {
|
|
// We don't support null targets yet.
|
|
aRv.Throw(NS_ERROR_DOM_ANIM_NO_TARGET_ERR);
|
|
return nullptr;
|
|
}
|
|
|
|
AnimationTiming timing = ConvertKeyframeEffectOptions(aOptions);
|
|
|
|
InfallibleTArray<AnimationProperty> animationProperties;
|
|
BuildAnimationPropertyList(aGlobal.Context(), aTarget, aFrames,
|
|
animationProperties, aRv);
|
|
|
|
if (aRv.Failed()) {
|
|
return nullptr;
|
|
}
|
|
|
|
RefPtr<KeyframeEffectReadOnly> effect =
|
|
new KeyframeEffectReadOnly(aTarget->OwnerDoc(), aTarget,
|
|
nsCSSPseudoElements::ePseudo_NotPseudoElement,
|
|
timing);
|
|
effect->mProperties = Move(animationProperties);
|
|
return effect.forget();
|
|
}
|
|
|
|
void
|
|
KeyframeEffectReadOnly::GetFrames(JSContext*& aCx,
|
|
nsTArray<JSObject*>& aResult,
|
|
ErrorResult& aRv)
|
|
{
|
|
nsTArray<OrderedKeyframeValueEntry> entries;
|
|
|
|
for (const AnimationProperty& property : mProperties) {
|
|
for (size_t i = 0, n = property.mSegments.Length(); i < n; i++) {
|
|
const AnimationPropertySegment& segment = property.mSegments[i];
|
|
|
|
// We append the mFromValue for each segment. If the mToValue
|
|
// differs from the following segment's mFromValue, or if we're on
|
|
// the last segment, then we append the mToValue as well.
|
|
//
|
|
// Each value is annotated with whether it is a "first", "left", "right",
|
|
// or "last" value. "left" and "right" values represent the value coming
|
|
// in to and out of a given offset, in the middle of an animation. For
|
|
// most segments, the mToValue is the "left" and the following segment's
|
|
// mFromValue is the "right". The "first" and "last" values are the
|
|
// additional values assigned to offset 0 or 1 for reverse and forward
|
|
// filling. These annotations are used to ensure multiple values for a
|
|
// given property are sorted correctly and that we do not merge Keyframes
|
|
// with different values for the same offset.
|
|
|
|
OrderedKeyframeValueEntry* entry = entries.AppendElement();
|
|
entry->mProperty = property.mProperty;
|
|
entry->mValue = segment.mFromValue;
|
|
entry->mOffset = segment.mFromKey;
|
|
entry->mTimingFunction = &segment.mTimingFunction;
|
|
entry->mPosition =
|
|
segment.mFromKey == segment.mToKey && segment.mFromKey == 0.0f ?
|
|
ValuePosition::First :
|
|
ValuePosition::Right;
|
|
|
|
if (i == n - 1 ||
|
|
segment.mToValue != property.mSegments[i + 1].mFromValue) {
|
|
entry = entries.AppendElement();
|
|
entry->mProperty = property.mProperty;
|
|
entry->mValue = segment.mToValue;
|
|
entry->mOffset = segment.mToKey;
|
|
entry->mTimingFunction = &segment.mTimingFunction;
|
|
entry->mPosition =
|
|
segment.mFromKey == segment.mToKey && segment.mToKey == 1.0f ?
|
|
ValuePosition::Last :
|
|
ValuePosition::Left;
|
|
}
|
|
}
|
|
}
|
|
|
|
entries.Sort(OrderedKeyframeValueEntry::ForKeyframeGenerationComparator());
|
|
|
|
for (size_t i = 0, n = entries.Length(); i < n; ) {
|
|
OrderedKeyframeValueEntry* entry = &entries[i];
|
|
OrderedKeyframeValueEntry* previousEntry = nullptr;
|
|
|
|
// Create a JS object with the explicit ComputedKeyframe dictionary members.
|
|
ComputedKeyframe keyframeDict;
|
|
keyframeDict.mOffset.SetValue(entry->mOffset);
|
|
keyframeDict.mComputedOffset.Construct(entry->mOffset);
|
|
if (entry->mTimingFunction) {
|
|
// If null, leave easing as its default "linear".
|
|
keyframeDict.mEasing.Truncate();
|
|
entry->mTimingFunction->AppendToString(keyframeDict.mEasing);
|
|
}
|
|
keyframeDict.mComposite.SetValue(CompositeOperation::Replace);
|
|
|
|
JS::Rooted<JS::Value> keyframeJSValue(aCx);
|
|
if (!ToJSValue(aCx, keyframeDict, &keyframeJSValue)) {
|
|
aRv.Throw(NS_ERROR_FAILURE);
|
|
return;
|
|
}
|
|
|
|
JS::Rooted<JSObject*> keyframe(aCx, &keyframeJSValue.toObject());
|
|
do {
|
|
const char* name = nsCSSProps::PropertyIDLName(entry->mProperty);
|
|
nsString stringValue;
|
|
StyleAnimationValue::UncomputeValue(entry->mProperty,
|
|
entry->mValue,
|
|
stringValue);
|
|
JS::Rooted<JS::Value> value(aCx);
|
|
if (!ToJSValue(aCx, stringValue, &value) ||
|
|
!JS_DefineProperty(aCx, keyframe, name, value, JSPROP_ENUMERATE)) {
|
|
aRv.Throw(NS_ERROR_FAILURE);
|
|
return;
|
|
}
|
|
if (++i == n) {
|
|
break;
|
|
}
|
|
previousEntry = entry;
|
|
entry = &entries[i];
|
|
} while (entry->SameKeyframe(*previousEntry));
|
|
|
|
aResult.AppendElement(keyframe);
|
|
}
|
|
}
|
|
|
|
/* static */ const TimeDuration
|
|
KeyframeEffectReadOnly::OverflowRegionRefreshInterval()
|
|
{
|
|
// The amount of time we can wait between updating throttled animations
|
|
// on the main thread that influence the overflow region.
|
|
static const TimeDuration kOverflowRegionRefreshInterval =
|
|
TimeDuration::FromMilliseconds(200);
|
|
|
|
return kOverflowRegionRefreshInterval;
|
|
}
|
|
|
|
bool
|
|
KeyframeEffectReadOnly::CanThrottle() const
|
|
{
|
|
// Animation::CanThrottle checks for not in effect animations
|
|
// before calling this.
|
|
MOZ_ASSERT(IsInEffect(), "Effect should be in effect");
|
|
|
|
// Unthrottle if this animation is not current (i.e. it has passed the end).
|
|
// In future we may be able to throttle this case too, but we should only get
|
|
// occasional ticks while the animation is in this state so it doesn't matter
|
|
// too much.
|
|
if (!IsCurrent()) {
|
|
return false;
|
|
}
|
|
|
|
nsIFrame* frame = GetAnimationFrame();
|
|
if (!frame) {
|
|
// There are two possible cases here.
|
|
// a) No target element
|
|
// b) The target element has no frame, e.g. because it is in a display:none
|
|
// subtree.
|
|
// In either case we can throttle the animation because there is no
|
|
// need to update on the main thread.
|
|
return true;
|
|
}
|
|
|
|
// First we need to check layer generation and transform overflow
|
|
// prior to the IsPropertyRunningOnCompositor check because we should
|
|
// occasionally unthrottle these animations even if the animations are
|
|
// already running on compositor.
|
|
for (const LayerAnimationInfo::Record& record :
|
|
LayerAnimationInfo::sRecords) {
|
|
// Skip properties that are overridden in the cascade.
|
|
// (GetAnimationOfProperty, as called by HasAnimationOfProperty,
|
|
// only returns an animation if it currently wins in the cascade.)
|
|
if (!HasAnimationOfProperty(record.mProperty)) {
|
|
continue;
|
|
}
|
|
|
|
AnimationCollection* collection = GetCollection();
|
|
MOZ_ASSERT(collection,
|
|
"CanThrottle should be called on an effect associated with an animation");
|
|
layers::Layer* layer =
|
|
FrameLayerBuilder::GetDedicatedLayer(frame, record.mLayerType);
|
|
// Unthrottle if the layer needs to be brought up to date with the animation.
|
|
if (!layer ||
|
|
collection->mAnimationGeneration > layer->GetAnimationGeneration()) {
|
|
return false;
|
|
}
|
|
|
|
// If this is a transform animation that affects the overflow region,
|
|
// we should unthrottle the animation periodically.
|
|
if (record.mProperty == eCSSProperty_transform &&
|
|
!CanThrottleTransformChanges(*frame)) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
for (const AnimationProperty& property : mProperties) {
|
|
if (!IsPropertyRunningOnCompositor(property.mProperty)) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool
|
|
KeyframeEffectReadOnly::CanThrottleTransformChanges(nsIFrame& aFrame) const
|
|
{
|
|
// If we know that the animation cannot cause overflow,
|
|
// we can just disable flushes for this animation.
|
|
|
|
// If we don't show scrollbars, we don't care about overflow.
|
|
if (LookAndFeel::GetInt(LookAndFeel::eIntID_ShowHideScrollbars) == 0) {
|
|
return true;
|
|
}
|
|
|
|
nsPresContext* presContext = GetPresContext();
|
|
// CanThrottleTransformChanges is only called as part of a refresh driver tick
|
|
// in which case we expect to has a pres context.
|
|
MOZ_ASSERT(presContext);
|
|
|
|
TimeStamp now =
|
|
presContext->RefreshDriver()->MostRecentRefresh();
|
|
|
|
AnimationCollection* collection = GetCollection();
|
|
MOZ_ASSERT(collection,
|
|
"CanThrottleTransformChanges should be involved with animation collection");
|
|
TimeStamp styleRuleRefreshTime = collection->mStyleRuleRefreshTime;
|
|
// If this animation can cause overflow, we can throttle some of the ticks.
|
|
if (!styleRuleRefreshTime.IsNull() &&
|
|
(now - styleRuleRefreshTime) < OverflowRegionRefreshInterval()) {
|
|
return true;
|
|
}
|
|
|
|
// If the nearest scrollable ancestor has overflow:hidden,
|
|
// we don't care about overflow.
|
|
nsIScrollableFrame* scrollable =
|
|
nsLayoutUtils::GetNearestScrollableFrame(&aFrame);
|
|
if (!scrollable) {
|
|
return true;
|
|
}
|
|
|
|
ScrollbarStyles ss = scrollable->GetScrollbarStyles();
|
|
if (ss.mVertical == NS_STYLE_OVERFLOW_HIDDEN &&
|
|
ss.mHorizontal == NS_STYLE_OVERFLOW_HIDDEN &&
|
|
scrollable->GetLogicalScrollPosition() == nsPoint(0, 0)) {
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
nsIFrame*
|
|
KeyframeEffectReadOnly::GetAnimationFrame() const
|
|
{
|
|
if (!mTarget) {
|
|
return nullptr;
|
|
}
|
|
|
|
nsIFrame* frame = mTarget->GetPrimaryFrame();
|
|
if (!frame) {
|
|
return nullptr;
|
|
}
|
|
|
|
if (mPseudoType == nsCSSPseudoElements::ePseudo_before) {
|
|
frame = nsLayoutUtils::GetBeforeFrame(frame);
|
|
} else if (mPseudoType == nsCSSPseudoElements::ePseudo_after) {
|
|
frame = nsLayoutUtils::GetAfterFrame(frame);
|
|
} else {
|
|
MOZ_ASSERT(mPseudoType == nsCSSPseudoElements::ePseudo_NotPseudoElement,
|
|
"unknown mPseudoType");
|
|
}
|
|
if (!frame) {
|
|
return nullptr;
|
|
}
|
|
|
|
return nsLayoutUtils::GetStyleFrame(frame);
|
|
}
|
|
|
|
nsIDocument*
|
|
KeyframeEffectReadOnly::GetRenderedDocument() const
|
|
{
|
|
if (!mTarget) {
|
|
return nullptr;
|
|
}
|
|
return mTarget->GetComposedDoc();
|
|
}
|
|
|
|
nsPresContext*
|
|
KeyframeEffectReadOnly::GetPresContext() const
|
|
{
|
|
nsIDocument* doc = GetRenderedDocument();
|
|
if (!doc) {
|
|
return nullptr;
|
|
}
|
|
nsIPresShell* shell = doc->GetShell();
|
|
if (!shell) {
|
|
return nullptr;
|
|
}
|
|
return shell->GetPresContext();
|
|
}
|
|
|
|
AnimationCollection *
|
|
KeyframeEffectReadOnly::GetCollection() const
|
|
{
|
|
return mAnimation ? mAnimation->GetCollection() : nullptr;
|
|
}
|
|
|
|
/* static */ bool
|
|
KeyframeEffectReadOnly::IsGeometricProperty(
|
|
const nsCSSProperty aProperty)
|
|
{
|
|
switch (aProperty) {
|
|
case eCSSProperty_bottom:
|
|
case eCSSProperty_height:
|
|
case eCSSProperty_left:
|
|
case eCSSProperty_right:
|
|
case eCSSProperty_top:
|
|
case eCSSProperty_width:
|
|
return true;
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/* static */ bool
|
|
KeyframeEffectReadOnly::CanAnimateTransformOnCompositor(
|
|
const nsIFrame* aFrame,
|
|
const nsIContent* aContent)
|
|
{
|
|
if (aFrame->Combines3DTransformWithAncestors() ||
|
|
aFrame->Extend3DContext()) {
|
|
if (aContent) {
|
|
nsCString message;
|
|
message.AppendLiteral("Gecko bug: Async animation of 'preserve-3d' "
|
|
"transforms is not supported. See bug 779598");
|
|
AnimationCollection::LogAsyncAnimationFailure(message, aContent);
|
|
}
|
|
return false;
|
|
}
|
|
// Note that testing BackfaceIsHidden() is not a sufficient test for
|
|
// what we need for animating backface-visibility correctly if we
|
|
// remove the above test for Extend3DContext(); that would require
|
|
// looking at backface-visibility on descendants as well.
|
|
if (aFrame->StyleDisplay()->BackfaceIsHidden()) {
|
|
if (aContent) {
|
|
nsCString message;
|
|
message.AppendLiteral("Gecko bug: Async animation of "
|
|
"'backface-visibility: hidden' transforms is not supported."
|
|
" See bug 1186204.");
|
|
AnimationCollection::LogAsyncAnimationFailure(message, aContent);
|
|
}
|
|
return false;
|
|
}
|
|
if (aFrame->IsSVGTransformed()) {
|
|
if (aContent) {
|
|
nsCString message;
|
|
message.AppendLiteral("Gecko bug: Async 'transform' animations of "
|
|
"aFrames with SVG transforms is not supported. See bug 779599");
|
|
AnimationCollection::LogAsyncAnimationFailure(message, aContent);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/* static */ bool
|
|
KeyframeEffectReadOnly::CanAnimatePropertyOnCompositor(
|
|
const nsIFrame* aFrame,
|
|
nsCSSProperty aProperty)
|
|
{
|
|
bool shouldLog = nsLayoutUtils::IsAnimationLoggingEnabled();
|
|
|
|
if (IsGeometricProperty(aProperty)) {
|
|
if (shouldLog) {
|
|
nsCString message;
|
|
message.AppendLiteral("Performance warning: Async animation of "
|
|
"'transform' or 'opacity' not possible due to animation of geometric"
|
|
"properties on the same element");
|
|
AnimationCollection::LogAsyncAnimationFailure(message,
|
|
aFrame->GetContent());
|
|
}
|
|
return false;
|
|
}
|
|
if (aProperty == eCSSProperty_transform) {
|
|
if (!CanAnimateTransformOnCompositor(aFrame,
|
|
shouldLog ? aFrame->GetContent() : nullptr)) {
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
} // namespace dom
|
|
} // namespace mozilla
|