Bug 194332 - CSS custom properties on pseudo elements background gradients causes infinite layout and high CPU load
Summary: CSS custom properties on pseudo elements background gradients causes infinite...
Status: NEW
Alias: None
Product: WebKit
Classification: Unclassified
Component: Animations (show other bugs)
Version: Safari 12
Hardware: All macOS 10.14
: P2 Normal
Assignee: Nobody
URL: https://jsfiddle.net/4gb9ynLz/
Keywords: InRadar
Depends on:
Blocks:
 
Reported: 2019-02-06 01:06 PST by Vladimir
Modified: 2019-05-08 11:49 PDT (History)
9 users (show)

See Also:


Attachments
Standalone version of the JSFiddle (1.77 KB, text/html)
2019-05-06 11:34 PDT, Simon Fraser (smfr)
no flags Details

Note You need to log in before you can comment on or make changes to this bug.
Description Vladimir 2019-02-06 01:06:16 PST
CSS custom properties used in gradient images on pseudo elements causes infinite relayout and high CPU load.

Reproduction scenario is simple:

<body>
  <style>
    :root {
      --link-highlight-color: rgba(0,0,0,0.1);
    }
    a {
      position: relative;
      min-width: 44px;
      height: 44px;
      display: inline-flex;
      justify-content: center;
      align-items: center;
      text-decoration: none;
    }
    a:before {
      content: '';
      width: 152%;
      height: 152%;
      position: absolute;
      left: -26%;
      top: -26%;
      background-image: radial-gradient(circle at center, var(--link-highlight-color) 66%, rgba(255,255,255,0) 66%);
      background-repeat: no-repeat;
      background-position: center;
      background-size: 100% 100%;
      opacity: 0;
      pointer-events: none;
      transition-duration: 600ms;
    }
    a:hover:before {
      opacity: 1;
    }
  </style>
  <p>
    <a href="#">Link 1</a>
  </p>
  <p>
    <a href="#">Another Link</a>
  </p>
  <p>
    <a href="#">Link 3</a>
  </p>
</body>
```

Here is the live example on JSFiddle https://jsfiddle.net/4gb9ynLz/

Such simple layout causes 100% CPU load on my system:

MacBook Pro 15 (Retina, Mid 2012)
macOs 10.14.2 (18C54)
Safari: 12.0.2 (14606.3.4)

Same happens on latest iOS 12.1.3 (16D39) Safari, iPhone X
Comment 1 Radar WebKit Bug Importer 2019-02-06 19:25:27 PST
<rdar://problem/47873895>
Comment 2 P. J. Łaszkowicz 2019-02-26 22:35:37 PST
This also occurs when loading background images. Replicated in a custom component where the background image of a button was repeatedly requested when a change to the button was expected. This could include a transform or simply a class change.

For example, the following snippet will cause the background image to reload constantly. 

button {
    background-color:    transparent;
    background-image:    var(--indago-action-menu-icon);
    transform: var(--indago-action-panel-transform); 
    transition: var(--indago-action-panel-transition); 
}

This occurs on both iOS and MacOS. Lesser impacts include flickering of the images due to unnecessary reloading, but the CPU and the GET requests are significantly more severe, even if less obvious to the end-user.

Other examples currently experienced include a reload on every :hover.
Comment 3 Don Denton 2019-05-05 15:42:19 PDT
It appears as though the alpha-channel is actually what is the final trigger for the problem. *Not* the custom property as the title of this bug indicates. Change the `rgba` functions to `rgb` in both locations and the problem vanishes.

I do believe that the `transition` is also necessary to reproduce.

Minimal repro: https://jsfiddle.net/0ph9xkrt/7/

In this example, when you hover over the paragraph, the rendering never ends.

It only works when all the following are true:

1. a transition is defined that is not instantaneous
2. there is an alpha channel in the color that is *exclusively* between 0 and 1
3. there is at least one color channel which is *exclusively* between 0 and 255.

The original example from this bug report pegs the CPU harder than mine, but I believe they are the same bug at its core.
Comment 4 Don Denton 2019-05-05 15:51:47 PDT
It appears as though the custom property is not what causes this bug as the title suggests. Here is a minimal repro, as far as I can tell:

https://jsfiddle.net/0ph9xkrt/7/

You need the following:

1. a transition is defined that is not instantaneous
2. there is an alpha channel in the color that is *exclusively* between 0 and 1
3. there is at least one color channel which is *exclusively* between 0 and 255

The linked example has some interactions you may observe.
Comment 5 Simon Fraser (smfr) 2019-05-06 11:34:30 PDT
Created attachment 369148 [details]
Standalone version of the JSFiddle
Comment 6 Simon Fraser (smfr) 2019-05-06 11:50:14 PDT
Animation logging shows us animating both box-shadow and -webkit-box-shadow. I wonder if this triggers the infinite animating:

Created ImplicitAnimation 0x10b4ec720 on element 0x133100070 for property box-shadow duration 0.50 delay 0.00
Created ImplicitAnimation 0x10b4ec7b8 on element 0x133100070 for property -webkit-box-shadow duration 0.50 delay 0.00
0x10b4ec720 AnimationState New -> New
0x10b4ec720 AnimationState New -> StartWaitTimer
  blending ShadowData at 0
0x10b4ec720 AnimationState StartWaitTimer -> StartWaitStyleAvailable (time is 0.000000)
0x10b4ec7b8 AnimationState New -> New
0x10b4ec7b8 AnimationState New -> StartWaitTimer
  blending ShadowData at 0
0x10b4ec7b8 AnimationState StartWaitTimer -> StartWaitStyleAvailable (time is 0.000000)
0x10b4ec720 AnimationState StartWaitStyleAvailable -> StartWaitResponse (time is -1.000000)
0x10b4ec7b8 AnimationState StartWaitStyleAvailable -> StartWaitResponse (time is -1.000000)
0x10b4ec720 AnimationState StartWaitResponse -> StartTimeSet (time is 582830.660499)
0x10b4ec720 AnimationState StartWaitResponse -> Ending
0x10b4ec7b8 AnimationState StartWaitResponse -> StartTimeSet (time is 582830.660499)
0x10b4ec7b8 AnimationState StartWaitResponse -> Ending
  blending ShadowData at 0.00
  blending ShadowData at 0.00
...
updateAnimationTimer: timeToNextService is 0.00
  blending ShadowData at 1.00
  blending ShadowData at 1.00
updateAnimationTimer: timeToNextService is 0.00
0x10b4ec720 AnimationState Ending -> Ending
0x10b4ec720 AnimationState Ending -> Done (time is 0.500000)
CSSAnimationControllerPrivate 0x10b496140 animationWillBeRemoved: 0x10b4ec720
Removing ImplicitAnimation 0x10b4ec720 from element 0x133100070 for property box-shadow
  blending ShadowData at 1.00
0x10b4ec7b8 AnimationState Ending -> Ending
0x10b4ec7b8 AnimationState Ending -> Done (time is 0.500000)
updateAnimationTimer: timeToNextService is 0.00
Created ImplicitAnimation 0x10b4ecd10 on element 0x133100070 for property box-shadow duration 0.50 delay 0.00
CSSAnimationControllerPrivate 0x10b496140 animationWillBeRemoved: 0x10b4ec7b8
Removing ImplicitAnimation 0x10b4ec7b8 from element 0x133100070 for property -webkit-box-shadow
0x10b4ecd10 AnimationState New -> New
0x10b4ecd10 AnimationState New -> StartWaitTimer
  blending ShadowData at 0
0x10b4ecd10 AnimationState StartWaitTimer -> StartWaitStyleAvailable (time is 0.000000)
0x10b4ecd10 AnimationState StartWaitStyleAvailable -> StartWaitResponse (time is -1.000000)
0x10b4ecd10 AnimationState StartWaitResponse -> StartTimeSet (time is 582831.176258)
0x10b4ecd10 AnimationState StartWaitResponse -> Ending
Created ImplicitAnimation 0x10b4ecda8 on element 0x133100070 for property -webkit-box-shadow duration 0.50 delay 0.00
  blending ShadowData at 0.00
0x10b4ecda8 AnimationState New -> New
0x10b4ecda8 AnimationState New -> StartWaitTimer
  blending ShadowData at 0
0x10b4ecda8 AnimationState StartWaitTimer -> StartWaitStyleAvailable (time is 0.000000)
0x10b4ecda8 AnimationState StartWaitStyleAvailable -> StartWaitResponse (time is -1.000000)
0x10b4ecda8 AnimationState StartWaitResponse -> StartTimeSet (time is 582831.177199)
0x10b4ecda8 AnimationState StartWaitResponse -> Ending
  blending ShadowData at 0.00
  blending ShadowData at 0.00
...
updateAnimationTimer: timeToNextService is 0.00
  blending ShadowData at 1.00
  blending ShadowData at 1.00
updateAnimationTimer: timeToNextService is 0.00
0x10b4ecd10 AnimationState Ending -> Ending
0x10b4ecd10 AnimationState Ending -> Done (time is 0.500000)
CSSAnimationControllerPrivate 0x10b496140 animationWillBeRemoved: 0x10b4ecd10
Removing ImplicitAnimation 0x10b4ecd10 from element 0x133100070 for property box-shadow
  blending ShadowData at 1.00
0x10b4ecda8 AnimationState Ending -> Ending
0x10b4ecda8 AnimationState Ending -> Done (time is 0.500000)
updateAnimationTimer: timeToNextService is 0.00
Created ImplicitAnimation 0x10b4ec688 on element 0x133100070 for property box-shadow duration 0.50 delay 0.00
CSSAnimationControllerPrivate 0x10b496140 animationWillBeRemoved: 0x10b4ecda8
Removing ImplicitAnimation 0x10b4ecda8 from element 0x133100070 for property -webkit-box-shadow
0x10b4ec688 AnimationState New -> New
0x10b4ec688 AnimationState New -> StartWaitTimer
  blending ShadowData at 0
0x10b4ec688 AnimationState StartWaitTimer -> StartWaitStyleAvailable (time is 0.000000)
0x10b4ec688 AnimationState StartWaitStyleAvailable -> StartWaitResponse (time is -1.000000)
0x10b4ec688 AnimationState StartWaitResponse -> StartTimeSet (time is 582831.683405)
0x10b4ec688 AnimationState StartWaitResponse -> Ending
Created ImplicitAnimation 0x10b4ec720 on element 0x133100070 for property -webkit-box-shadow duration 0.50 delay 0.00
  blending ShadowData at 0.00
0x10b4ec720 AnimationState New -> New
0x10b4ec720 AnimationState New -> StartWaitTimer
  blending ShadowData at 0
0x10b4ec720 AnimationState StartWaitTimer -> StartWaitStyleAvailable (time is 0.000000)
0x10b4ec720 AnimationState StartWaitStyleAvailable -> StartWaitResponse (time is -1.000000)
0x10b4ec720 AnimationState StartWaitResponse -> StartTimeSet (time is 582831.684502)
0x10b4ec720 AnimationState StartWaitResponse -> Ending
  blending ShadowData at 0.00
  blending ShadowData at 0.00
...
Comment 7 Jon Lee 2019-05-08 11:49:14 PDT
I'm not seeing the same CPU load with the latest JSFiddle or the standalone example. The original example still appears to show 100% CPU however.