Bug 1210796 - Part 1a: Add GetUnanimatedComputedStyle into nsIDOMWindowUtils to use in animationinspector of devtools. r=birtles,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

--HG--
extra : rebase_source : 64df3850b862dada5f8e0767d47dc478d454ba31
This commit is contained in:
Daisuke Akatsuka 2017-04-18 12:15:47 +09:00
Родитель da3730147e
Коммит 088af889f0
7 изменённых файлов: 269 добавлений и 39 удалений

Просмотреть файл

@ -2782,6 +2782,72 @@ nsDOMWindowUtils::GetAnimationTypeForLonghand(const nsAString& aProperty,
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,

Просмотреть файл

@ -151,10 +151,130 @@ function test_getAnimationType() {
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() {

Просмотреть файл

@ -1564,6 +1564,17 @@ interface nsIDOMWindowUtils : nsISupports {
*/
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.
*/

Просмотреть файл

@ -12,6 +12,7 @@
#include "nsAtomListUtils.h"
#include "nsStaticAtom.h"
#include "nsCSSAnonBoxes.h"
#include "nsDOMString.h"
using namespace mozilla;
@ -117,6 +118,39 @@ nsCSSPseudoElements::GetPseudoAtom(Type aType)
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)
{

Просмотреть файл

@ -92,6 +92,9 @@ public:
// 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);
}

Просмотреть файл

@ -12,7 +12,6 @@
#include "mozilla/Preferences.h"
#include "nsError.h"
#include "nsDOMString.h"
#include "nsIDOMCSSPrimitiveValue.h"
#include "nsIFrame.h"
#include "nsIFrameInlines.h"
@ -67,10 +66,12 @@ using namespace mozilla::dom;
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();
}
@ -243,7 +244,8 @@ nsComputedStyleMap::Update()
nsComputedDOMStyle::nsComputedDOMStyle(dom::Element* aElement,
const nsAString& aPseudoElt,
nsIPresShell* aPresShell)
nsIPresShell* aPresShell,
AnimationFlag aFlag)
: mDocumentWeak(nullptr)
, mOuterFrame(nullptr)
, mInnerFrame(nullptr)
@ -251,38 +253,13 @@ nsComputedDOMStyle::nsComputedDOMStyle(dom::Element* aElement,
, 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());
}
@ -847,6 +824,17 @@ nsComputedDOMStyle::UpdateCurrentStyleSources(bool aNeedsLayoutFlush)
"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(),

Просмотреть файл

@ -69,9 +69,15 @@ public:
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
{
@ -82,11 +88,6 @@ public:
GetStyleContext(mozilla::dom::Element* aElement, nsIAtom* aPseudo,
nsIPresShell* aPresShell);
enum AnimationFlag {
eWithAnimation,
eWithoutAnimation,
};
static already_AddRefed<nsStyleContext>
GetStyleContextNoFlush(mozilla::dom::Element* aElement,
nsIAtom* aPseudo,
@ -747,6 +748,11 @@ private:
*/
bool mResolvedStyleContext;
/**
* Whether we include animation rules in the computed style.
*/
AnimationFlag mAnimationFlag;
#ifdef DEBUG
bool mFlushedPendingReflows;
#endif
@ -755,6 +761,8 @@ private:
already_AddRefed<nsComputedDOMStyle>
NS_NewComputedDOMStyle(mozilla::dom::Element* aElement,
const nsAString& aPseudoElt,
nsIPresShell* aPresShell);
nsIPresShell* aPresShell,
nsComputedDOMStyle::AnimationFlag aFlag =
nsComputedDOMStyle::eWithAnimation);
#endif /* nsComputedDOMStyle_h__ */