"""Decide whether a dump is blank/empty-shell and should not become a flow step. Only applied to browser surface. Does NOT skip same-URL scrolls (content may change without URL change). """ from __future__ import annotations from typing import Any, Optional import target # Locatable below this counts as "no useful candidates" _MIN_LOCATABLE = 3 # Tree/index tiny enough to treat as empty shell _MAX_EMPTY_NODES = 2 def _norm_url(url: str) -> str: return (url or "").strip() def is_blankish_url(url: str) -> bool: u = _norm_url(url).lower() if not u: return True if u == "about:blank" or u.startswith("about:blank?"): return True if u in {"about:srcdoc", "about:newtab"}: return True if u.startswith("chrome://newtab") or u.startswith("chrome://new-tab-page"): return True if u.startswith("edge://newtab") or u.startswith("edge://new-tab-page"): return True return False def should_skip_empty_dump( *, surface: str, url: str = "", locatable_count: Optional[int] = None, nodes_kept: Optional[int] = None, sparse: bool = False, locatable: Optional[list[dict[str, Any]]] = None, stats: Optional[dict[str, Any]] = None, ) -> Optional[str]: """Return skip reason code, or None if the dump should be kept. Safe rules (browser only): - blank/newtab URL **and** almost no locatable → skip - tiny tree + sparse/blankish + almost no locatable → skip A page with about:blank URL but rich locatable (nav race) is **kept**. """ if surface != target.SURFACE_BROWSER: return None st = stats or {} if locatable_count is None: if locatable is not None: locatable_count = len(locatable) else: locatable_count = int(st.get("locatable_count") or 0) if nodes_kept is None: nodes_kept = int(st.get("nodes_kept") or st.get("findall_count") or 0) if not sparse: sparse = bool(st.get("uia_sparse")) loc_n = int(locatable_count or 0) kept = int(nodes_kept or 0) blankish = is_blankish_url(url) if blankish and loc_n < _MIN_LOCATABLE: return "blank_or_newtab_empty" if loc_n < _MIN_LOCATABLE and kept <= _MAX_EMPTY_NODES and (sparse or blankish): return "empty_shell" return None def skip_reason_text(reason: str, *, url: str = "") -> str: u = _norm_url(url) or "(empty)" if reason == "blank_or_newtab_empty": return f"跳过空页:url={u!r} 且可定位候选过少(blank/新标签页)。" if reason == "empty_shell": return f"跳过空壳页:url={u!r} 节点过少且 sparse。" return f"跳过本拍:{reason} url={u!r}"