bump
This commit is contained in:
@@ -9149,30 +9149,30 @@ func (d *Daemon) checkContrast(tabID string, selector string, timeout int) (*Con
|
||||
}
|
||||
}
|
||||
|
||||
// Tier 3: Gradient analysis
|
||||
// Tier 3: Gradient analysis - walks up DOM tree to find gradient backgrounds
|
||||
function analyzeGradientContrast(element, textColor) {
|
||||
try {
|
||||
const style = window.getComputedStyle(element);
|
||||
// Walk up the DOM tree to find a gradient background
|
||||
let current = element;
|
||||
while (current && current !== document.documentElement.parentElement) {
|
||||
const style = window.getComputedStyle(current);
|
||||
const bgImage = style.backgroundImage;
|
||||
|
||||
// Check if background is a gradient
|
||||
if (!bgImage || !bgImage.includes('gradient')) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (bgImage && bgImage.includes('gradient')) {
|
||||
// Extract gradient colors using regex
|
||||
const colorRegex = /rgba?\([^)]+\)|#[0-9a-f]{3,6}/gi;
|
||||
const matches = bgImage.match(colorRegex) || [];
|
||||
// Fix incomplete rgb/rgba matches by adding closing paren
|
||||
const colors = matches.map(c => c.includes('(') && !c.includes(')') ? c + ')' : c);
|
||||
|
||||
if (colors.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (colors.length > 0) {
|
||||
// Calculate contrast against each color in the gradient
|
||||
const fg = parseColor(textColor);
|
||||
if (!fg) return null;
|
||||
if (!fg) {
|
||||
current = current.parentElement;
|
||||
continue;
|
||||
}
|
||||
|
||||
const contrastRatios = [];
|
||||
const parsedColors = [];
|
||||
@@ -9186,10 +9186,7 @@ func (d *Daemon) checkContrast(tabID string, selector string, timeout int) (*Con
|
||||
}
|
||||
}
|
||||
|
||||
if (contrastRatios.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (contrastRatios.length > 0) {
|
||||
// Return worst-case (minimum) contrast ratio
|
||||
const worstCase = Math.min(...contrastRatios);
|
||||
const bestCase = Math.max(...contrastRatios);
|
||||
@@ -9200,6 +9197,14 @@ func (d *Daemon) checkContrast(tabID string, selector string, timeout int) (*Con
|
||||
colors: parsedColors,
|
||||
isGradient: true
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
current = current.parentElement;
|
||||
}
|
||||
|
||||
return null;
|
||||
} catch (e) {
|
||||
return null;
|
||||
}
|
||||
@@ -9293,7 +9298,7 @@ func (d *Daemon) checkContrast(tabID string, selector string, timeout int) (*Con
|
||||
let detectionMethod = null;
|
||||
let gradientInfo = null;
|
||||
|
||||
// Tier 1: DOM tree walking
|
||||
// Tier 1: DOM tree walking for solid colors
|
||||
bgColor = getEffectiveBackgroundDOMTree(element);
|
||||
if (bgColor) {
|
||||
detectionMethod = 'dom-tree';
|
||||
@@ -9307,12 +9312,14 @@ func (d *Daemon) checkContrast(tabID string, selector string, timeout int) (*Con
|
||||
}
|
||||
}
|
||||
|
||||
// Tier 3: Check for gradient backgrounds
|
||||
if (bgColor) {
|
||||
// Tier 3: Check for gradient backgrounds (always check, even if solid color found)
|
||||
// Gradients take precedence over solid colors when present
|
||||
gradientInfo = analyzeGradientContrast(element, fgColor);
|
||||
if (gradientInfo) {
|
||||
detectionMethod = 'gradient-analysis';
|
||||
}
|
||||
// Use a representative color from the gradient for the background_color field
|
||||
// We'll use the worst-case color for reporting purposes
|
||||
bgColor = gradientInfo.colors[0] || bgColor;
|
||||
}
|
||||
|
||||
// Final fallback to white if no background detected
|
||||
|
||||
83
docs/contrast_check_fix_container_elements.md
Normal file
83
docs/contrast_check_fix_container_elements.md
Normal file
@@ -0,0 +1,83 @@
|
||||
# Contrast Check Fix: Container Element False Positives
|
||||
|
||||
## Issue Description
|
||||
|
||||
The automated contrast checker was incorrectly flagging parent container elements (DIVs) instead of the actual text elements (H2, P, etc.) when checking color contrast.
|
||||
|
||||
### Example from the Field
|
||||
|
||||
On https://visionleadership.org/gala-sponsorship/:
|
||||
|
||||
**Element 18 (H2 heading):**
|
||||
- Tag: `H2`
|
||||
- Text: "Gala Presenting Sponsor"
|
||||
- Color: `rgb(255, 255, 255)` ✅ WHITE
|
||||
- Background: `rgba(0, 0, 0, 0)` - transparent
|
||||
- Parent background: `rgb(12, 113, 195)` - BLUE
|
||||
- **Actual contrast ratio: 5.04:1 ✅ PASSES AA**
|
||||
|
||||
**Element 17 (parent DIV):**
|
||||
- Tag: `DIV`
|
||||
- Color: `rgb(61, 61, 61)` - DARK GRAY
|
||||
- Background: `rgb(12, 113, 195)` - BLUE
|
||||
- **Computed contrast ratio: 2.15:1 ❌ FALSE POSITIVE**
|
||||
|
||||
### Root Cause
|
||||
|
||||
The contrast checker was using the selector:
|
||||
```javascript
|
||||
"p, h1, h2, h3, h4, h5, h6, a, button, span, div, li, td, th, label, input, textarea"
|
||||
```
|
||||
|
||||
When a DIV container had text content (from child elements like H2), it was being checked using the DIV's computed text color (dark gray) instead of the actual H2's color (white).
|
||||
|
||||
## Solution
|
||||
|
||||
Added logic to skip container elements (DIV, SPAN) that don't have **direct text nodes** (text that is a direct child, not in descendant elements).
|
||||
|
||||
### Implementation
|
||||
|
||||
```javascript
|
||||
// Skip container elements that don't have direct text nodes
|
||||
const hasDirectTextContent = Array.from(element.childNodes).some(node =>
|
||||
node.nodeType === Node.TEXT_NODE && node.textContent.trim().length > 0
|
||||
);
|
||||
|
||||
// For container elements (div, span), only check if they have direct text
|
||||
// For semantic text elements (h1-h6, p, a, button, etc.), always check
|
||||
const isSemanticTextElement = ['H1', 'H2', 'H3', 'H4', 'H5', 'H6', 'P', 'A', 'BUTTON', 'LABEL', 'LI', 'TD', 'TH'].includes(element.tagName);
|
||||
const isContainerElement = ['DIV', 'SPAN'].includes(element.tagName);
|
||||
|
||||
if (isContainerElement && !hasDirectTextContent) {
|
||||
// Skip this container - its children will be checked separately
|
||||
return;
|
||||
}
|
||||
```
|
||||
|
||||
### Behavior After Fix
|
||||
|
||||
1. **Semantic text elements** (H1-H6, P, A, BUTTON, LABEL, LI, TD, TH) are **always checked**, regardless of whether they have direct text content
|
||||
2. **Container elements** (DIV, SPAN) are **only checked if they have direct text nodes**
|
||||
3. **Container elements without direct text** are **skipped** - their child elements will be checked separately
|
||||
|
||||
## Test Results
|
||||
|
||||
After the fix, the contrast check correctly identifies:
|
||||
|
||||
- ✅ H2 "Gala Presenting Sponsor": White text on blue background = **5.04:1 PASSES AA**
|
||||
- ✅ H2 "Gala Bar Sponsor": White text on blue background = **5.04:1 PASSES AA**
|
||||
- ✅ H2 "Table Sponsor": White text on blue background = **5.04:1 PASSES AA**
|
||||
- ✅ H2 "RSVP Gala Ticket": White text on blue background = **5.04:1 PASSES AA**
|
||||
- ✅ Parent DIVs are **not checked** (correctly skipped)
|
||||
|
||||
## Files Modified
|
||||
|
||||
- `daemon/daemon.go` - Lines 9221-9241: Added logic to skip container elements without direct text content
|
||||
|
||||
## Impact
|
||||
|
||||
- **Eliminates false positives** from parent containers
|
||||
- **Improves accuracy** of contrast checking
|
||||
- **Reduces noise** in accessibility reports
|
||||
- **No impact** on legitimate contrast violations
|
||||
|
||||
Reference in New Issue
Block a user