Bug 1210796 - Part 1a: Add GetUnanimatedComputedStyle into nsIDOMWindowUtils to use in animationinspector of devtools. r=birtles,r=heycam
In this patch, we implement nsIDOMWindowUtils::GetUnanimatedComputedStyle
which returns computed value of given CSS property without animation rule. This
method is used from the DevTools animation inspector to fill in keyframe values
when the property value is null (indicating that the underlying/base value is
being used).
In order to implement this, we extend nsComputedDOMStyle constructor to fetch
the computed style minus animation style (i.e. the base style). This is somewhat
complicated by the fact that for discrete animation.
StyleAnimationValue::ExtractComputedValue may return ‘unset’, ‘initial’ or
‘inherit’. For example, if the author uses the 'unset' 'initial' or 'inherit'
keyword for a discrete property (e.g. 'align-content’), ExtractComputedValue
returns the keywords as-is. Furthermore, if the user does not set any specific
keyword, ExtractComputedValue returns ‘unset’. We use this new
nsComputedDOMStyle mechanism to resolve these keywords into a valid keyword for
computed style in the same way as other properties (e.g. ‘opacity’).
MozReview-Commit-ID: HffJ9SCDf2k
--- a/dom/base/nsDOMWindowUtils.cpp
+++ b/dom/base/nsDOMWindowUtils.cpp
@@ -2777,16 +2777,82 @@ nsDOMWindowUtils::GetAnimationTypeForLon
break;
case eStyleAnimType_None:
aResult.AssignLiteral("none");
break;
}
return NS_OK;
}
+NS_IMETHODIMP
+nsDOMWindowUtils::GetUnanimatedComputedStyle(nsIDOMElement* aElement,
+ const nsAString& aPseudoElement,
+ const nsAString& aProperty,
+ nsAString& aResult)
+{
+ nsCOMPtr<Element> element = do_QueryInterface(aElement);
+ if (!element) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ nsCSSPropertyID propertyID =
+ nsCSSProps::LookupProperty(aProperty, CSSEnabledState::eForAllContent);
+ if (propertyID == eCSSProperty_UNKNOWN ||
+ nsCSSProps::IsShorthand(propertyID)) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ nsIPresShell* shell = GetPresShell();
+ if (!shell) {
+ return NS_ERROR_FAILURE;
+ }
+
+ nsIAtom* pseudo = nsCSSPseudoElements::GetPseudoAtom(aPseudoElement);
+ RefPtr<nsStyleContext> styleContext =
+ nsComputedDOMStyle::GetUnanimatedStyleContextNoFlush(element,
+ pseudo, shell);
+
+ // We will support Servo in bug 1311257.
+ if (shell->StyleSet()->IsServo()) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+ }
+
+ StyleAnimationValue computedValue;
+ if (!StyleAnimationValue::ExtractComputedValue(propertyID,
+ styleContext, computedValue)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ // Note: ExtractComputedValue can return 'unset', 'initial', or 'inherit' in
+ // its "computedValue" outparam, even though these technically aren't valid
+ // computed values. (It has this behavior for discretely-animatable
+ // properties, e.g. 'align-content', when these keywords are explicitly
+ // specified or when there is no specified value.) But we need to return a
+ // valid computed value -- these keywords won't do. So we fall back to
+ // nsComputedDOMStyle in this case.
+ if (computedValue.GetUnit() == StyleAnimationValue::eUnit_DiscreteCSSValue &&
+ (computedValue.GetCSSValueValue()->GetUnit() == eCSSUnit_Unset ||
+ computedValue.GetCSSValueValue()->GetUnit() == eCSSUnit_Initial ||
+ computedValue.GetCSSValueValue()->GetUnit() == eCSSUnit_Inherit)) {
+ RefPtr<nsComputedDOMStyle> computedStyle =
+ NS_NewComputedDOMStyle(
+ element, aPseudoElement, shell,
+ nsComputedDOMStyle::AnimationFlag::eWithoutAnimation);
+ computedStyle->GetPropertyValue(propertyID, aResult);
+ return NS_OK;
+ }
+
+ DebugOnly<bool> uncomputeResult =
+ StyleAnimationValue::UncomputeValue(propertyID,
+ Move(computedValue), aResult);
+ MOZ_ASSERT(uncomputeResult,
+ "Unable to get specified value from computed value");
+ return NS_OK;
+}
+
nsresult
nsDOMWindowUtils::RenderDocument(const nsRect& aRect,
uint32_t aFlags,
nscolor aBackgroundColor,
gfxContext* aThebesContext)
{
nsCOMPtr<nsIDocument> doc = GetDocument();
NS_ENSURE_TRUE(doc, NS_ERROR_FAILURE);
--- a/dom/base/test/test_domwindowutils.html
+++ b/dom/base/test/test_domwindowutils.html
@@ -146,20 +146,140 @@ function test_getAnimationType() {
SimpleTest.doesThrow(
() => utils.getAnimationTypeForLonghand("invalid"),
"NS_ERROR_ILLEGAL_VALUE",
"Invalid property should throw");
next();
}
+function test_getUnanimatedComputedStyle() {
+ [
+ {
+ property: "opacity",
+ keyframes: [1, 0],
+ expectedInitialStyle: "1",
+ expectedDuringTransitionStyle: "0",
+ isDiscrete: false,
+ },
+ {
+ property: "clear",
+ keyframes: ["left", "inline-end"],
+ expectedInitialStyle: "none",
+ expectedDuringTransitionStyle: "inline-end",
+ isDiscrete: true,
+ },
+ ].forEach(testcase => {
+ const { property, keyframes, expectedInitialStyle,
+ expectedDuringTransitionStyle, isDiscrete } = testcase;
+
+ [null, "unset", "initial", "inherit"].forEach(initialStyle => {
+ const scriptAnimation = target => {
+ return target.animate({ [property]: keyframes }, 1000);
+ }
+ checkUnanimatedComputedStyle(property, initialStyle, null,
+ expectedInitialStyle, expectedInitialStyle,
+ scriptAnimation, "script animation");
+
+ const cssAnimationStyle = `@keyframes cssanimation {`
+ + ` from { ${property}: ${ keyframes[0] }; }`
+ + ` to { ${property}: ${ keyframes[1] }; } }`;
+ document.styleSheets[0].insertRule(cssAnimationStyle, 0);
+ const cssAnimation = target => {
+ target.style.animation = "cssanimation 1s";
+ return target.getAnimations()[0];
+ }
+ checkUnanimatedComputedStyle(property, initialStyle, null,
+ expectedInitialStyle, expectedInitialStyle,
+ cssAnimation, "CSS Animations");
+ document.styleSheets[0].deleteRule(0);
+
+ // We don't support discrete animations for CSS Transitions yet.
+ // (bug 1320854)
+ if (!isDiscrete) {
+ const cssTransition = target => {
+ target.style[property] = keyframes[0];
+ target.style.transition =
+ `${ property } 1s`;
+ window.getComputedStyle(target)[property];
+ target.style[property] = keyframes[1];
+ return target.getAnimations()[0];
+ }
+ checkUnanimatedComputedStyle(property, initialStyle, null,
+ expectedInitialStyle,
+ expectedDuringTransitionStyle,
+ cssTransition, "CSS Transitions");
+ }
+
+ document.styleSheets[0].insertRule(cssAnimationStyle, 0);
+ document.styleSheets[0].insertRule(
+ ".pseudo::before { animation: cssanimation 1s; }", 0);
+ const pseudoAnimation = target => {
+ target.classList.add("pseudo");
+ return target.getAnimations({ subtree: true })[0];
+ }
+ checkUnanimatedComputedStyle(property, initialStyle, "::before",
+ expectedInitialStyle, expectedInitialStyle,
+ pseudoAnimation, "Animation at pseudo");
+ document.styleSheets[0].deleteRule(0);
+ document.styleSheets[0].deleteRule(0);
+ });
+ });
+
+ SimpleTest.doesThrow(
+ () => utils.getUnanimatedComputedStyle(div, null, "background"),
+ "NS_ERROR_INVALID_ARG",
+ "Shorthand property should throw");
+
+ SimpleTest.doesThrow(
+ () => utils.getUnanimatedComputedStyle(div, null, "invalid"),
+ "NS_ERROR_INVALID_ARG",
+ "Invalid property should throw");
+
+ SimpleTest.doesThrow(
+ () => utils.getUnanimatedComputedStyle(null, null, "opacity"),
+ "NS_ERROR_INVALID_ARG",
+ "Null element should throw");
+
+ next();
+}
+
+function checkUnanimatedComputedStyle(property, initialStyle, pseudoType,
+ expectedBeforeAnimation,
+ expectedDuringAnimation,
+ animate, animationType) {
+ const div = document.createElement("div");
+ document.body.appendChild(div);
+
+ if (initialStyle) {
+ div.style[property] = initialStyle;
+ }
+
+ is(utils.getUnanimatedComputedStyle(div, pseudoType, property),
+ expectedBeforeAnimation,
+ `'${ property }' property with '${ initialStyle }' style `
+ + `should be '${ expectedBeforeAnimation }' `
+ + `before animating by ${ animationType }`);
+
+ const animation = animate(div);
+ animation.currentTime = 500;
+ is(utils.getUnanimatedComputedStyle(div, pseudoType, property),
+ expectedDuringAnimation,
+ `'${ property }' property with '${ initialStyle }' style `
+ + `should be '${ expectedDuringAnimation }' `
+ + `even while animating by ${ animationType }`);
+
+ div.remove();
+}
+
var tests = [
test_sendMouseEventDefaults,
test_sendMouseEventOptionals,
- test_getAnimationType
+ test_getAnimationType,
+ test_getUnanimatedComputedStyle
];
function next() {
if (!tests.length) {
SimpleTest.finish();
return;
}
--- a/dom/interfaces/base/nsIDOMWindowUtils.idl
+++ b/dom/interfaces/base/nsIDOMWindowUtils.idl
@@ -1560,16 +1560,27 @@ interface nsIDOMWindowUtils : nsISupport
/**
* Returns the animation type of the specified property (e.g. 'coord').
*
* @param aProperty A longhand CSS property (e.g. 'background-color').
*/
AString getAnimationTypeForLonghand(in AString aProperty);
/**
+ * Returns the computed style for the specified property of given pseudo type
+ * on the given element after removing styles from declarative animations.
+ * @param aElement - A target element
+ * @param aPseudoElement - A pseudo type (e.g. '::before' or null)
+ * @param aProperty - A longhand CSS property (e.g. 'background-color')
+ */
+ AString getUnanimatedComputedStyle(in nsIDOMElement aElement,
+ in AString aPseudoElement,
+ in AString aProperty);
+
+ /**
* Get the type of the currently focused html input, if any.
*/
readonly attribute string focusedInputType;
/**
* Find the view ID for a given element. This is the reverse of
* findElementWithViewId().
*/
--- a/layout/style/nsCSSPseudoElements.cpp
+++ b/layout/style/nsCSSPseudoElements.cpp
@@ -7,16 +7,17 @@
#include "nsCSSPseudoElements.h"
#include "mozilla/ArrayUtils.h"
#include "nsAtomListUtils.h"
#include "nsStaticAtom.h"
#include "nsCSSAnonBoxes.h"
+#include "nsDOMString.h"
using namespace mozilla;
// define storage for all atoms
#define CSS_PSEUDO_ELEMENT(name_, value_, flags_) \
nsICSSPseudoElement* nsCSSPseudoElements::name_;
#include "nsCSSPseudoElementList.h"
#undef CSS_PSEUDO_ELEMENT
@@ -112,14 +113,47 @@ nsCSSPseudoElements::GetPseudoType(nsIAt
/* static */ nsIAtom*
nsCSSPseudoElements::GetPseudoAtom(Type aType)
{
NS_ASSERTION(aType < Type::Count, "Unexpected type");
return *CSSPseudoElements_info[
static_cast<CSSPseudoElementTypeBase>(aType)].mAtom;
}
+/* static */ nsIAtom*
+nsCSSPseudoElements::GetPseudoAtom(const nsAString& aPseudoElement)
+{
+ if (DOMStringIsNull(aPseudoElement) || aPseudoElement.IsEmpty() ||
+ aPseudoElement.First() != char16_t(':')) {
+ return nullptr;
+ }
+
+ // deal with two-colon forms of aPseudoElt
+ nsAString::const_iterator start, end;
+ aPseudoElement.BeginReading(start);
+ aPseudoElement.EndReading(end);
+ NS_ASSERTION(start != end, "aPseudoElement is not empty!");
+ ++start;
+ bool haveTwoColons = true;
+ if (start == end || *start != char16_t(':')) {
+ --start;
+ haveTwoColons = false;
+ }
+ nsCOMPtr<nsIAtom> pseudo = NS_Atomize(Substring(start, end));
+ MOZ_ASSERT(pseudo);
+
+ // There aren't any non-CSS2 pseudo-elements with a single ':'
+ if (!haveTwoColons &&
+ (!IsPseudoElement(pseudo) || !IsCSS2PseudoElement(pseudo))) {
+ // XXXbz I'd really rather we threw an exception or something, but
+ // the DOM spec sucks.
+ return nullptr;
+ }
+
+ return pseudo;
+}
+
/* static */ bool
nsCSSPseudoElements::PseudoElementSupportsUserActionState(const Type aType)
{
return PseudoElementHasFlags(aType,
CSS_PSEUDO_ELEMENT_SUPPORTS_USER_ACTION_STATE);
}
--- a/layout/style/nsCSSPseudoElements.h
+++ b/layout/style/nsCSSPseudoElements.h
@@ -87,16 +87,19 @@ public:
#include "nsCSSPseudoElementList.h"
#undef CSS_PSEUDO_ELEMENT
static Type GetPseudoType(nsIAtom* aAtom, EnabledState aEnabledState);
// Get the atom for a given Type. aType must be < CSSPseudoElementType::Count
static nsIAtom* GetPseudoAtom(Type aType);
+ // Get the atom for a given nsAString. (e.g. "::before")
+ static nsIAtom* GetPseudoAtom(const nsAString& aPseudoElement);
+
static bool PseudoElementContainsElements(const Type aType) {
return PseudoElementHasFlags(aType, CSS_PSEUDO_ELEMENT_CONTAINS_ELEMENTS);
}
static bool PseudoElementSupportsStyleAttribute(const Type aType) {
MOZ_ASSERT(aType < Type::Count);
return PseudoElementHasFlags(aType,
CSS_PSEUDO_ELEMENT_SUPPORTS_STYLE_ATTRIBUTE);
--- a/layout/style/nsComputedDOMStyle.cpp
+++ b/layout/style/nsComputedDOMStyle.cpp
@@ -7,17 +7,16 @@
/* DOM object returned from element.getComputedStyle() */
#include "nsComputedDOMStyle.h"
#include "mozilla/ArrayUtils.h"
#include "mozilla/Preferences.h"
#include "nsError.h"
-#include "nsDOMString.h"
#include "nsIDOMCSSPrimitiveValue.h"
#include "nsIFrame.h"
#include "nsIFrameInlines.h"
#include "nsStyleContext.h"
#include "nsIScrollableFrame.h"
#include "nsContentUtils.h"
#include "nsIContent.h"
@@ -62,20 +61,22 @@ using namespace mozilla::dom;
/*
* This is the implementation of the readonly CSSStyleDeclaration that is
* returned by the getComputedStyle() function.
*/
already_AddRefed<nsComputedDOMStyle>
NS_NewComputedDOMStyle(dom::Element* aElement, const nsAString& aPseudoElt,
- nsIPresShell* aPresShell)
+ nsIPresShell* aPresShell,
+ nsComputedDOMStyle::AnimationFlag aFlag)
{
RefPtr<nsComputedDOMStyle> computedStyle;
- computedStyle = new nsComputedDOMStyle(aElement, aPseudoElt, aPresShell);
+ computedStyle = new nsComputedDOMStyle(aElement, aPseudoElt,
+ aPresShell, aFlag);
return computedStyle.forget();
}
static nsDOMCSSValueList*
GetROCSSValueList(bool aCommaDelimited)
{
return new nsDOMCSSValueList(aCommaDelimited, true);
}
@@ -238,56 +239,32 @@ nsComputedStyleMap::Update()
mIndexMap[index++] = i;
}
}
mExposedPropertyCount = index;
}
nsComputedDOMStyle::nsComputedDOMStyle(dom::Element* aElement,
const nsAString& aPseudoElt,
- nsIPresShell* aPresShell)
+ nsIPresShell* aPresShell,
+ AnimationFlag aFlag)
: mDocumentWeak(nullptr)
, mOuterFrame(nullptr)
, mInnerFrame(nullptr)
, mPresShell(nullptr)
, mStyleContextGeneration(0)
, mExposeVisitedStyle(false)
, mResolvedStyleContext(false)
+ , mAnimationFlag(aFlag)
{
MOZ_ASSERT(aElement && aPresShell);
mDocumentWeak = do_GetWeakReference(aPresShell->GetDocument());
-
mContent = aElement;
-
- if (!DOMStringIsNull(aPseudoElt) && !aPseudoElt.IsEmpty() &&
- aPseudoElt.First() == char16_t(':')) {
- // deal with two-colon forms of aPseudoElt
- nsAString::const_iterator start, end;
- aPseudoElt.BeginReading(start);
- aPseudoElt.EndReading(end);
- NS_ASSERTION(start != end, "aPseudoElt is not empty!");
- ++start;
- bool haveTwoColons = true;
- if (start == end || *start != char16_t(':')) {
- --start;
- haveTwoColons = false;
- }
- mPseudo = NS_Atomize(Substring(start, end));
- MOZ_ASSERT(mPseudo);
-
- // There aren't any non-CSS2 pseudo-elements with a single ':'
- if (!haveTwoColons &&
- (!nsCSSPseudoElements::IsPseudoElement(mPseudo) ||
- !nsCSSPseudoElements::IsCSS2PseudoElement(mPseudo))) {
- // XXXbz I'd really rather we threw an exception or something, but
- // the DOM spec sucks.
- mPseudo = nullptr;
- }
- }
+ mPseudo = nsCSSPseudoElements::GetPseudoAtom(aPseudoElt);
MOZ_ASSERT(aPresShell->GetPresContext());
}
nsComputedDOMStyle::~nsComputedDOMStyle()
{
ClearStyleContext();
}
@@ -842,16 +819,27 @@ nsComputedDOMStyle::UpdateCurrentStyleSo
mPresShell->GetPresContext()->GetRestyleGeneration(),
"why should we have flushed style again?");
SetResolvedStyleContext(Move(resolvedStyleContext));
NS_ASSERTION(mPseudo || !mStyleContext->HasPseudoElementData(),
"should not have pseudo-element data");
}
+ if (mAnimationFlag == eWithoutAnimation) {
+ // We will support Servo in bug 1311257.
+ MOZ_ASSERT(mPresShell->StyleSet()->IsGecko(),
+ "eWithoutAnimationRules support Gecko only");
+ nsStyleSet* styleSet = mPresShell->StyleSet()->AsGecko();
+ RefPtr<nsStyleContext> unanimatedStyleContext =
+ styleSet->ResolveStyleByRemovingAnimation(
+ mContent->AsElement(), mStyleContext, eRestyle_AllHintsWithAnimations);
+ SetResolvedStyleContext(Move(unanimatedStyleContext));
+ }
+
// mExposeVisitedStyle is set to true only by testing APIs that
// require chrome privilege.
MOZ_ASSERT(!mExposeVisitedStyle || nsContentUtils::IsCallerChrome(),
"mExposeVisitedStyle set incorrectly");
if (mExposeVisitedStyle && mStyleContext->RelevantLinkVisited()) {
nsStyleContext *styleIfVisited = mStyleContext->GetStyleIfVisited();
if (styleIfVisited) {
mStyleContext = styleIfVisited;
--- a/layout/style/nsComputedDOMStyle.h
+++ b/layout/style/nsComputedDOMStyle.h
@@ -64,34 +64,35 @@ public:
NS_DECL_NSIDOMCSSSTYLEDECLARATION_HELPER
virtual already_AddRefed<CSSValue>
GetPropertyCSSValue(const nsAString& aProp, mozilla::ErrorResult& aRv)
override;
using nsICSSDeclaration::GetPropertyCSSValue;
virtual void IndexedGetter(uint32_t aIndex, bool& aFound, nsAString& aPropName) override;
+ enum AnimationFlag {
+ eWithAnimation,
+ eWithoutAnimation,
+ };
+
nsComputedDOMStyle(mozilla::dom::Element* aElement,
const nsAString& aPseudoElt,
- nsIPresShell* aPresShell);
+ nsIPresShell* aPresShell,
+ AnimationFlag aFlag = eWithAnimation);
virtual nsINode *GetParentObject() override
{
return mContent;
}
static already_AddRefed<nsStyleContext>
GetStyleContext(mozilla::dom::Element* aElement, nsIAtom* aPseudo,
nsIPresShell* aPresShell);
- enum AnimationFlag {
- eWithAnimation,
- eWithoutAnimation,
- };
-
static already_AddRefed<nsStyleContext>
GetStyleContextNoFlush(mozilla::dom::Element* aElement,
nsIAtom* aPseudo,
nsIPresShell* aPresShell)
{
return DoGetStyleContextNoFlush(aElement,
aPseudo,
aPresShell,
@@ -742,19 +743,26 @@ private:
bool mExposeVisitedStyle;
/**
* Whether we resolved a style context last time we called
* UpdateCurrentStyleSources. Initially false.
*/
bool mResolvedStyleContext;
+ /**
+ * Whether we include animation rules in the computed style.
+ */
+ AnimationFlag mAnimationFlag;
+
#ifdef DEBUG
bool mFlushedPendingReflows;
#endif
};
already_AddRefed<nsComputedDOMStyle>
NS_NewComputedDOMStyle(mozilla::dom::Element* aElement,
const nsAString& aPseudoElt,
- nsIPresShell* aPresShell);
+ nsIPresShell* aPresShell,
+ nsComputedDOMStyle::AnimationFlag aFlag =
+ nsComputedDOMStyle::eWithAnimation);
#endif /* nsComputedDOMStyle_h__ */