Bug 119479

Summary: Inserting a JS generated keyframe animation shouldn't trigger a whole document style recalc
Product: WebKit Reporter: Ralph T <ralpht+bugs>
Component: CSSAssignee: Nobody <webkit-unassigned>
Status: RESOLVED FIXED    
Severity: Normal CC: buildbot, commit-queue, dino, esprehn+autocc, glenn, gyuyoung.kim, kling, koivisto, macpherson, menard, ralpht+bugs, rniwa, simon.fraser
Priority: P2    
Version: 528+ (Nightly build)   
Hardware: Unspecified   
OS: Unspecified   
Attachments:
Description Flags
Example of adding an animation via JS which triggers a whole document style recalc
none
Better/slower test using CSSStyleSheet.insertRule.
none
Patch
none
Patch
none
Patch
none
Patch
none
Patch none

Ralph T
Reported 2013-08-04 13:34:25 PDT
Created attachment 208099 [details] Example of adding an animation via JS which triggers a whole document style recalc Inserting a keyframe animation created in JS causes a full style recalc which can be very expensive on large documents. WebKit doesn't start an animation if it was referenced by a node prior to inserting it, so hopefully it's possible to add a custom keyframe animation for only the time it takes to parse it. The attached example causes a longer style recalc (on L26) when the keyframe rule is added.
Attachments
Example of adding an animation via JS which triggers a whole document style recalc (936 bytes, text/html)
2013-08-04 13:34 PDT, Ralph T
no flags
Better/slower test using CSSStyleSheet.insertRule. (2.33 KB, text/html)
2013-10-02 13:36 PDT, Ralph T
no flags
Patch (3.80 KB, patch)
2013-10-02 13:48 PDT, Ralph T
no flags
Patch (6.07 KB, patch)
2013-10-02 14:51 PDT, Ralph T
no flags
Patch (5.73 KB, patch)
2013-10-03 12:10 PDT, Ralph T
no flags
Patch (9.59 KB, patch)
2013-10-03 17:35 PDT, Ralph T
no flags
Patch (10.71 KB, patch)
2013-10-04 11:35 PDT, Ralph T
no flags
Ralph T
Comment 1 2013-10-02 13:36:38 PDT
Created attachment 213191 [details] Better/slower test using CSSStyleSheet.insertRule. This test inserts lots of elements with a style that's expensive to calculate (as seen in the timeline view), then when you click the red box it inserts a @-webkit-keyframes rule into an existing stylesheet and sets the box's webkitAnimationName to use the new rule. Currently inserting the new rule causes an invalidation of the style of every node.
Ralph T
Comment 2 2013-10-02 13:48:39 PDT
Ralph T
Comment 3 2013-10-02 13:49:27 PDT
Comment on attachment 213194 [details] Patch Clear review flag.
Andreas Kling
Comment 4 2013-10-02 14:00:34 PDT
Comment on attachment 213194 [details] Patch View in context: https://bugs.webkit.org/attachment.cgi?id=213194&action=review > Source/WebCore/css/CSSStyleSheet.cpp:317 > + if (owner) > + owner->ensureStyleResolver().addKeyframeStyle(static_cast<StyleRuleKeyframes*>(rule.get())); If there is no StyleResolver here, I don't think we need to create one. Use Document::styleResolverIfExists() instead, the keyframes rule will get picked up during the inevitable StyleResolver construction.
Antti Koivisto
Comment 5 2013-10-02 14:01:24 PDT
Comment on attachment 213194 [details] Patch View in context: https://bugs.webkit.org/attachment.cgi?id=213194&action=review > Source/WebCore/css/CSSStyleSheet.cpp:318 > + if (rule->type() == CSSRule::WEBKIT_KEYFRAMES_RULE) { > + Document* owner = ownerDocument(); > + if (owner) > + owner->ensureStyleResolver().addKeyframeStyle(static_cast<StyleRuleKeyframes*>(rule.get())); > + } I think this can go to CSSStyleSheet::didMutateRules.
Antti Koivisto
Comment 6 2013-10-02 14:02:49 PDT
The idea is right assuming there is no way for keyframe insertion to trigger animation or generally affect anything that would require style recalc.
Ralph T
Comment 7 2013-10-02 14:04:36 PDT
The CSS3 Animations spec says about the "animation-name" property: "If the name does not match any keyframe at-rule, there are no properties to be animated and the animation will not execute." So if you have an element that already matches the animation name you're inserting then nothing will happen.
Build Bot
Comment 8 2013-10-02 14:44:31 PDT
Ralph T
Comment 9 2013-10-02 14:51:00 PDT
Ralph T
Comment 10 2013-10-02 14:53:11 PDT
The latest patch uses styleResolverIfExists() and moves the insertion into CSSStyleSheet::didMutateRules. Next I will see what we have so far as tests for dynamic animation insertion and locally run LayoutTests on a debug build (of EFL) in case the change causes new asserts.
Darin Adler
Comment 11 2013-10-02 16:01:06 PDT
Comment on attachment 213201 [details] Patch View in context: https://bugs.webkit.org/attachment.cgi?id=213201&action=review This seems like a great optimization. I think there is a slightly tighter way to code it. > Source/WebCore/css/CSSStyleSheet.cpp:175 > + ASSERT(insertedRule->type() == StyleRuleBase::Keyframes); > + StyleResolver* resolver = 0; > + if (owner && (resolver = owner->styleResolverIfExists())) > + resolver->addKeyframeStyle(static_cast<StyleRuleKeyframes*>(insertedRule)); It’s not helpful to initialize resolver to 0 since that value is never used. And also, this coding style is a bit tortured. It doesn’t really make sense to null-check owner here since we already checked that earlier. I think it should end up like this: if (StyleResolver* resolver = owner->styleResolverIfExists()) return resolver->addKeyframeStyle(insertedRule); If we did need a null check for owner, I would have suggested an early return. > Source/WebCore/css/CSSStyleSheet.cpp:427 > + , m_insertedRule(0) Please use nullptr instead of 0 in new code. > Source/WebCore/css/CSSStyleSheet.h:88 > + enum RuleMutationType { OtherMutation, RuleInsertion, KeyframesRuleInsertion }; I don’t think a specific type of rule should be treated as a separate mutation type, especially if we have to pass the actual rule in, I suggest just using RuleInsertion combined with an inserted keyframes rule. Or eliminate the concept of RuleMutationType entirely (see below). > Source/WebCore/css/CSSStyleSheet.h:94 > + RuleMutationScope(CSSStyleSheet*, RuleMutationType = OtherMutation, StyleRuleBase* insertedRule = 0); Please use nullptr instead of 0 in new code. I suggest we use the type StyleRuleKeyframes* here instead of StyleRuleBase*. If we decide to use this in a more general way later, we could change that, but no reason to be needlessly general. Another way to go would be to get rid of RuleMutationType entirely, and only pass in the inserted rule. Then a rule of nullptr would mean “other mutation”. Either way would be OK with me.
Antti Koivisto
Comment 12 2013-10-02 17:15:27 PDT
Comment on attachment 213201 [details] Patch Yeah, looks good except for the now-unnecessary KeyframesRuleInsertion value. We may still want to keep the RuleMutationType enum for clarity.
Ralph T
Comment 13 2013-10-03 12:10:46 PDT
Ralph T
Comment 14 2013-10-03 12:32:49 PDT
(In reply to comment #11) Thanks for the review, Darin! > > Source/WebCore/css/CSSStyleSheet.cpp:175 > > + ASSERT(insertedRule->type() == StyleRuleBase::Keyframes); > > + StyleResolver* resolver = 0; > > + if (owner && (resolver = owner->styleResolverIfExists())) > > + resolver->addKeyframeStyle(static_cast<StyleRuleKeyframes*>(insertedRule)); > > It’s not helpful to initialize resolver to 0 since that value is never used. And also, this coding style is a bit tortured. It doesn’t really make sense to null-check owner here since we already checked that earlier. I think it should end up like this: > > if (StyleResolver* resolver = owner->styleResolverIfExists()) > return resolver->addKeyframeStyle(insertedRule); > > If we did need a null check for owner, I would have suggested an early return. I reworked this and folded into the existing RuleInsertion branch since they're now share the same conditions. It's a lot better now :). > > > Source/WebCore/css/CSSStyleSheet.cpp:427 > > + , m_insertedRule(0) > > Please use nullptr instead of 0 in new code. Done. > > Source/WebCore/css/CSSStyleSheet.h:94 > > + RuleMutationScope(CSSStyleSheet*, RuleMutationType = OtherMutation, StyleRuleBase* insertedRule = 0); > > Please use nullptr instead of 0 in new code. > Done. > I suggest we use the type StyleRuleKeyframes* here instead of StyleRuleBase*. If we decide to use this in a more general way later, we could change that, but no reason to be needlessly general. I did this, and changed the name to m_insertedKeyframesRule. > Another way to go would be to get rid of RuleMutationType entirely, and only pass in the inserted rule. Then a rule of nullptr would mean “other mutation”. Either way would be OK with me. I kept the enum because maybe there will be other optimized cases in the future (like deleting keyframes?). I still plan on making a new test for dynamically creating animations (I haven't done this before; it's going to look a bit like animations/change-keyframes.html).
Antti Koivisto
Comment 15 2013-10-03 12:54:27 PDT
Comment on attachment 213285 [details] Patch View in context: https://bugs.webkit.org/attachment.cgi?id=213285&action=review r=me > Source/WebCore/ChangeLog:9 > + https://bugs.webkit.org/show_bug.cgi?id=119479 > + > + Change CSSStyleSheet::didMutateRules to not invalidate all node's styles when inserting a > + @-webkit-keyframes rule, and to instead insert the rule directly into the StyleResolver. > + > + Reviewed by NOBODY (OOPS!). Reviewed-by line usually goes after the URL, before the descriptive text. > Source/WebCore/ChangeLog:11 > + No new tests (OOPS!). Some tests for dynamic insertions (and deletions too) would be nice.
Ralph T
Comment 16 2013-10-03 17:35:44 PDT
Ralph T
Comment 17 2013-10-03 17:39:37 PDT
Most recent patch includes a test case which adds some keyframes rules and removes them before and after the animation has started. Unfortunately I was unable to produce a working build of DumpRenderTree or WKTR, so I haven't been able to generate expected results. Antti, would you be able to do this for me? (I'm not sure of the process here, this is the first time I've added a test). I will keep trying to make a working WKTR in the meantime.
Ralph T
Comment 18 2013-10-04 11:35:33 PDT
Ralph T
Comment 19 2013-10-04 11:41:18 PDT
Ok, I figured out my WKTR woes and built expected results for the test. Antti -- is this what you had in mind for tests? If so could you set r+ and cq+ for me (I'm not a committer).
WebKit Commit Bot
Comment 20 2013-10-04 13:45:34 PDT
Comment on attachment 213382 [details] Patch Clearing flags on attachment: 213382 Committed r156912: <http://trac.webkit.org/changeset/156912>
WebKit Commit Bot
Comment 21 2013-10-04 13:45:37 PDT
All reviewed patches have been landed. Closing bug.
Simon Fraser (smfr)
Comment 22 2013-10-07 12:48:32 PDT
I'm confused about the behavior with this patch. If an element has: -webkit-animation-name: foo and you insert @-webkit-keyframes foo {...}, does the animation start?
Ralph T
Comment 23 2013-10-07 13:43:25 PDT
(In reply to comment #22) > I'm confused about the behavior with this patch. If an element has: -webkit-animation-name: foo and you insert @-webkit-keyframes foo {...}, does the animation start? It does not, and it didn't before this patch either. I think the relevant part of the CSS 3 Animations Spec is "If the name does not match any keyframe at-rule, there are no properties to be animated and the animation will not execute." I read that to mean that at the time the webkitAnimationName property is set if there's no keyframes at-rule with that name then it's ignored, but it's not explicitly spelled out. I don't believe the patch changed any WebKit behavior, however. I can definitely add some more test cases around this, or raise the issue on www-style.
Simon Fraser (smfr)
Comment 24 2013-10-07 14:01:47 PDT
(In reply to comment #23) > I can definitely add some more test cases around this, or raise the issue on www-style. Please do raise it on www-style so the behavior is specified.
Ralph T
Comment 25 2013-10-07 16:32:01 PDT
(In reply to comment #24) > (In reply to comment #23) > > I can definitely add some more test cases around this, or raise the issue on www-style. > > Please do raise it on www-style so the behavior is specified. Ok, raised: http://lists.w3.org/Archives/Public/www-style/2013Oct/0248.html As I mentioned in the email, I also tested in Firefox and that does start the animation when just adding the @keyframes rule (also they're very quick starting the animation, so I'd like to learn what's going on there!).
Ralph T
Comment 26 2013-10-07 17:53:10 PDT
Ok, so I was mistaken and an animation should start in the case Simon described -- and there's even a bug for it: https://bugs.webkit.org/show_bug.cgi?id=27881 .. Oops! I'll follow up on that bug.
Note You need to log in before you can comment on or make changes to this bug.