Skip to content

target-size: false positive when translucent target is inside a positioned "fake" stacking context #4350

@dbjorge

Description

@dbjorge

Product

axe-core

Product Version

4.8.4

Latest Version

  • I have tested the issue with the latest version of the product

Issue Description

We got a customer report today of a target-size false positive (see minimal repro below). This turned out to be because the stacking context tracking used by the target-offset check has an issue where it only has a partial understanding of the rules for elements with opacity < 1. Per https://www.w3.org/TR/css-color-3/#transparency:

Since an element with opacity less than 1 is composited from a single offscreen image, content outside of it cannot be layered in z-order between pieces of content inside of it. For the same reason, implementations must create a new stacking context for any element with opacity less than 1. If an element with opacity less than 1 is not positioned, then it is painted on the same layer, within its parent stacking context, as positioned elements with stack level 0. If an element with opacity less than 1 is positioned, the ‘z-index’ property applies as described in [CSS21], except that if the used value is ‘auto’ then the element behaves exactly as if it were ‘0’. See section 9.9 and Appendix E of [CSS21] for more information on stacking contexts. The rules in this paragraph do not apply to SVG elements, since SVG has its own rendering model ([SVG11], Chapter 3).

Currently, create-grid.js's isStackingContext does understand that opacity < 1 should results in a stacking context being created, but getStackLevel doesn't understand the rule buried in the middle about non-positioned translucent elements being painted alongside positioned elements. The code in createStackingOrder that distinguished fake and true stacks also doesn't understand to treat opacity-induced stacking contexts as "true" contexts, which the spec suggests we're supposed to do even when we draw them in POSITION_LEVEL order.

In the repro case below (and the internally-reported customer site), this results in hasVisualOverlap (via visuallySort) incorrectly determining that the positioned parent completely overlaps and obscures the translucent child, which isn't what happens in browsers in practice. This in turn results in getTargetRects returning an empty list such that #4194 gets triggered (which in turn results in the target-size false positive).

I titled this bug according to the user-facing impact, but let's have this one just track the bad stack context behavior; #4194 already tracks the bad getTargetRects/targetOffsetEvaluate behavior.

Expectation

Repro case below shouldn't violate the target-size rule.

Actual

It does.

How to Reproduce

<div style="position: absolute; transform: scale(1)">
  <button id="t1">first tab</button>
  <button id="t2" style="opacity: .6">second tab</button>
</div>

Additional context

Metadata

Metadata

Assignees

No one assigned

    Labels

    fixBug fixesrulesIssue or false result from an axe-core rulesupporttarget-size

    Type

    No type

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions