"""Pry — Shadow DOM flattening utilities. Recursively extracts content from shadow roots in HTML.""" # SPDX-License-Identifier: BSL-1.1 # Copyright (c) 2026 Rug Munch Media LLC # # Part of Pry — Stealth / Anti-Detection Module # Licensed under Business Source License 1.1 — see LICENSE-BSL-STEALTH. # Change Date: 2029-01-01 (converts to MIT). import logging import re logger = logging.getLogger(__name__) # Regex to detect shadow DOM in raw HTML SHADOW_ROOT_PATTERNS = [ r"]*shadowroot[^>]*>", r"#shadow-root", r"attachshadow", r"shadowroot", r"open-mode", r"closed-mode", ] def has_shadow_dom(html: str) -> bool: """Check if HTML contains Shadow DOM content.""" lower = html.lower() return any(re.search(p, lower) for p in SHADOW_ROOT_PATTERNS) def flatten_shadow_dom(html: str) -> str: """Flatten shadow DOM templates into the main document. Converts: To: content (inline, no template wrapper) Also handles declarative shadow DOM (', r"\1", html, count=0, flags=re.DOTALL, ) # Pattern 2: result = re.sub( r']*>(.*?)', r"\1", result, count=0, flags=re.DOTALL, ) # Pattern 3: result = re.sub( r'(.*?)', r"\1", result, count=0, flags=re.DOTALL, ) # Pattern 4:
#shadow-root (open)
# (this is a comment/annotation, not real shadow DOM — leave as-is) logger.debug( "shadow_dom_flattened", extra={"original_size": len(html), "new_size": len(result)} ) return result class ShadowDOMProcessor: """Process HTML to extract content from shadow DOM components. Usage: processor = ShadowDOMProcessor() flat_html = processor.process(original_html) """ def __init__(self, flatten_declarative: bool = True): self.flatten_declarative = flatten_declarative def process(self, html: str) -> str: """Flatten shadow DOM in HTML content.""" if not self._detect(html): return html result = html if self.flatten_declarative: result = flatten_shadow_dom(result) # Additional processing: extract inline styles from shadow roots result = self._extract_shadow_styles(result) return result def _detect(self, html: str) -> bool: return has_shadow_dom(html) def _extract_shadow_styles(self, html: str) -> str: """Extract ", r"", html, flags=re.DOTALL, ) return result