Bug 1018269: Handle Shadow DOM in keyframes lookup. r?heycam
MozReview-Commit-ID: AeUmsOHOUYR
--- a/layout/style/ServoBindingList.h
+++ b/layout/style/ServoBindingList.h
@@ -130,16 +130,17 @@ SERVO_BINDING_FUNC(Servo_StyleSet_FlushS
SERVO_BINDING_FUNC(Servo_StyleSet_SetAuthorStyleDisabled, void,
RawServoStyleSetBorrowed set,
bool author_style_disabled)
SERVO_BINDING_FUNC(Servo_StyleSet_NoteStyleSheetsChanged, void,
RawServoStyleSetBorrowed set,
mozilla::OriginFlags changed_origins)
SERVO_BINDING_FUNC(Servo_StyleSet_GetKeyframesForName, bool,
RawServoStyleSetBorrowed set,
+ RawGeckoElementBorrowed element,
nsAtom* name,
nsTimingFunctionBorrowed timing_function,
RawGeckoKeyframeListBorrowedMut keyframe_list)
SERVO_BINDING_FUNC(Servo_StyleSet_GetFontFaceRules, void,
RawServoStyleSetBorrowed set,
RawGeckoFontFaceRuleListBorrowedMut list)
SERVO_BINDING_FUNC(Servo_StyleSet_GetCounterStyleRule,
const RawServoCounterStyleRule*,
--- a/layout/style/ServoStyleSet.cpp
+++ b/layout/style/ServoStyleSet.cpp
@@ -1172,24 +1172,26 @@ ServoStyleSet::AssertTreeIsClean()
DocumentStyleRootIterator iter(mDocument);
while (Element* root = iter.GetNextStyleRoot()) {
Servo_AssertTreeIsClean(root);
}
}
#endif
bool
-ServoStyleSet::GetKeyframesForName(nsAtom* aName,
+ServoStyleSet::GetKeyframesForName(const Element& aElement,
+ nsAtom* aName,
const nsTimingFunction& aTimingFunction,
nsTArray<Keyframe>& aKeyframes)
{
// TODO(emilio): This may need to look at the element itself for handling
// @keyframes properly in Shadow DOM.
MOZ_ASSERT(!StylistNeedsUpdate());
return Servo_StyleSet_GetKeyframesForName(mRawSet.get(),
+ &aElement,
aName,
&aTimingFunction,
&aKeyframes);
}
nsTArray<ComputedKeyframeValues>
ServoStyleSet::GetComputedKeyframeValuesFor(
const nsTArray<Keyframe>& aKeyframes,
--- a/layout/style/ServoStyleSet.h
+++ b/layout/style/ServoStyleSet.h
@@ -336,17 +336,18 @@ public:
* Resolve style for the given element, and return it as a
* ComputedStyle.
*
* FIXME(emilio): Is there a point in this after bug 1367904?
*/
inline already_AddRefed<ComputedStyle>
ResolveServoStyle(dom::Element* aElement);
- bool GetKeyframesForName(nsAtom* aName,
+ bool GetKeyframesForName(const dom::Element& aElement,
+ nsAtom* aName,
const nsTimingFunction& aTimingFunction,
nsTArray<Keyframe>& aKeyframes);
nsTArray<ComputedKeyframeValues>
GetComputedKeyframeValuesFor(const nsTArray<Keyframe>& aKeyframes,
dom::Element* aElement,
const mozilla::ComputedStyle* aStyle);
--- a/layout/style/nsAnimationManager.cpp
+++ b/layout/style/nsAnimationManager.cpp
@@ -352,24 +352,26 @@ PopExistingAnimation(const nsAtom* aName
class MOZ_STACK_CLASS ServoCSSAnimationBuilder final {
public:
explicit ServoCSSAnimationBuilder(const ComputedStyle* aComputedStyle)
: mComputedStyle(aComputedStyle)
{
MOZ_ASSERT(aComputedStyle);
}
- bool BuildKeyframes(nsPresContext* aPresContext,
+ bool BuildKeyframes(const Element& aElement,
+ nsPresContext* aPresContext,
nsAtom* aName,
const nsTimingFunction& aTimingFunction,
nsTArray<Keyframe>& aKeyframes)
{
ServoStyleSet* styleSet = aPresContext->StyleSet();
MOZ_ASSERT(styleSet);
- return styleSet->GetKeyframesForName(aName,
+ return styleSet->GetKeyframesForName(aElement,
+ aName,
aTimingFunction,
aKeyframes);
}
void SetKeyframes(KeyframeEffectReadOnly& aEffect,
nsTArray<Keyframe>&& aKeyframes)
{
aEffect.SetKeyframes(Move(aKeyframes), mComputedStyle);
}
@@ -478,17 +480,18 @@ BuildAnimation(nsPresContext* aPresConte
uint32_t animIdx,
ServoCSSAnimationBuilder& aBuilder,
nsAnimationManager::CSSAnimationCollection* aCollection)
{
MOZ_ASSERT(aPresContext);
nsAtom* animationName = aStyleDisplay.GetAnimationName(animIdx);
nsTArray<Keyframe> keyframes;
- if (!aBuilder.BuildKeyframes(aPresContext,
+ if (!aBuilder.BuildKeyframes(*aTarget.mElement,
+ aPresContext,
animationName,
aStyleDisplay.GetAnimationTimingFunction(animIdx),
keyframes)) {
return nullptr;
}
TimingParams timing =
TimingParamsFromCSSParams(aStyleDisplay.GetAnimationDuration(animIdx),
--- a/servo/components/style/stylist.rs
+++ b/servo/components/style/stylist.rs
@@ -1416,20 +1416,29 @@ impl Stylist {
CaseSensitivity::CaseSensitive => {},
}
let hash = id.get_hash();
self.any_applicable_rule_data(element, |data| data.mapped_ids.might_contain_hash(hash))
}
/// Returns the registered `@keyframes` animation for the specified name.
- ///
- /// FIXME(emilio): This needs to account for the element rules.
#[inline]
- pub fn get_animation(&self, name: &Atom) -> Option<&KeyframesAnimation> {
+ pub fn get_animation<'a, E>(
+ &'a self,
+ name: &Atom,
+ element: E,
+ ) -> Option<&'a KeyframesAnimation>
+ where
+ E: TElement + 'a,
+ {
+ if let Some(shadow) = element.containing_shadow() {
+ return shadow.style_data().animations.get(name)
+ }
+
self.cascade_data
.iter_origins()
.filter_map(|(d, _)| d.animations.get(name))
.next()
}
/// Computes the match results of a given element against the set of
/// revalidation selectors.
--- a/servo/ports/geckolib/glue.rs
+++ b/servo/ports/geckolib/glue.rs
@@ -4763,27 +4763,28 @@ fn fill_in_missing_keyframe_values(
}
}
}
}
#[no_mangle]
pub unsafe extern "C" fn Servo_StyleSet_GetKeyframesForName(
raw_data: RawServoStyleSetBorrowed,
+ element: RawGeckoElementBorrowed,
name: *mut nsAtom,
inherited_timing_function: nsTimingFunctionBorrowed,
keyframes: RawGeckoKeyframeListBorrowedMut,
) -> bool {
- debug_assert!(keyframes.len() == 0,
- "keyframes should be initially empty");
-
+ debug_assert!(keyframes.len() == 0, "keyframes should be initially empty");
+
+ let element = GeckoElement(element);
let data = PerDocumentStyleData::from_ffi(raw_data).borrow();
let name = Atom::from_raw(name);
- let animation = match data.stylist.get_animation(&name) {
+ let animation = match data.stylist.get_animation(&name, element) {
Some(animation) => animation,
None => return false,
};
let global_style_data = &*GLOBAL_STYLE_DATA;
let guard = global_style_data.shared_lock.read();
let mut properties_set_at_current_offset = LonghandIdSet::new();
--- a/testing/web-platform/meta/MANIFEST.json
+++ b/testing/web-platform/meta/MANIFEST.json
@@ -316152,16 +316152,28 @@
]
],
"css/css-scoping/host-functional-descendant-invalidation.html": [
[
"/css/css-scoping/host-functional-descendant-invalidation.html",
{}
]
],
+ "css/css-scoping/keyframes-001.html": [
+ [
+ "/css/css-scoping/keyframes-001.html",
+ {}
+ ]
+ ],
+ "css/css-scoping/keyframes-002.html": [
+ [
+ "/css/css-scoping/keyframes-002.html",
+ {}
+ ]
+ ],
"css/css-scoping/shadow-cascade-order-001.html": [
[
"/css/css-scoping/shadow-cascade-order-001.html",
{}
]
],
"css/css-scoping/slotted-invalidation.html": [
[
@@ -511913,16 +511925,24 @@
"css/css-scoping/host-nested-001.html": [
"a0b74d2e6bf24e9142904a925f95e969d206db20",
"reftest"
],
"css/css-scoping/host-slotted-001.html": [
"918bd04b95a276c6035383f2fe4dcfe4274bceeb",
"reftest"
],
+ "css/css-scoping/keyframes-001.html": [
+ "f0cda2f0f3d4093c7a24d283416c4fead20e597f",
+ "testharness"
+ ],
+ "css/css-scoping/keyframes-002.html": [
+ "748ba6ae7c4f34770652c3269b92ff004e71e542",
+ "testharness"
+ ],
"css/css-scoping/reference/green-box.html": [
"a736f68dc602c0fccab56ec5cc6234cb3298c88d",
"support"
],
"css/css-scoping/shadow-assign-dynamic-001.html": [
"c57e0fd5aa5be63e1cadf65a4e382798c5e05ec4",
"reftest"
],
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/css/css-scoping/keyframes-001.html
@@ -0,0 +1,38 @@
+<!doctype html>
+<title>CSS Test: @keyframes applies in the shadow tree.</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<link rel="author" title="Emilio Cobos Álvarez" href="mailto:emilio@crisal.io">
+<link rel="help" href="https://drafts.csswg.org/css-scoping/#host-selector">
+<style>
+#in-document {
+ width: 100px;
+ height: 100px;
+ background: blue;
+ animation: myanim 10s infinite;
+}
+</style>
+<div id="host"><div id="in-document"></div></div>
+<script>
+test(function() {
+ host.attachShadow({ mode: "open" }).innerHTML = `
+ <style>
+ @keyframes myanim {
+ from { background: red; }
+ to { background: green; }
+ }
+ #in-shadow {
+ width: 100px;
+ height: 100px;
+ background: blue;
+ animation: myanim 10s infinite;
+ }
+ </style>
+ <slot></slot>
+ <div id="in-shadow"></div>
+ `;
+
+ assert_equals(document.getElementById('in-document').getAnimations().length, 0);
+ assert_equals(host.shadowRoot.getElementById('in-shadow').getAnimations().length, 1);
+}, "@keyframes applies in the shadow tree")
+</script>
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/css/css-scoping/keyframes-002.html
@@ -0,0 +1,30 @@
+<!doctype html>
+<title>CSS Test: @keyframes from the document don't apply in the shadow tree.</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<link rel="author" title="Emilio Cobos Álvarez" href="mailto:emilio@crisal.io">
+<link rel="help" href="https://drafts.csswg.org/css-scoping/#host-selector">
+<style>
+@keyframes myanim {
+ from { background: red; }
+ to { background: green; }
+}
+</style>
+<div id="host"></div>
+<script>
+test(function() {
+ host.attachShadow({ mode: "open" }).innerHTML = `
+ <style>
+ #in-shadow {
+ width: 100px;
+ height: 100px;
+ background: blue;
+ animation: myanim 10s infinite;
+ }
+ </style>
+ <div id="in-shadow"></div>
+ `;
+
+ assert_equals(host.shadowRoot.getElementById('in-shadow').getAnimations().length, 0);
+}, "@keyframes from the document don't apply in the shadow tree");
+</script>