"""Match a requested URL against already-open tabs. Agents and scripts usually want the tab that already shows a page instead of a duplicate of it. These helpers answer "is this tab already (nearly) the URL I am about to open?" without touching the browser. Modes: ``none`` never reuse a tab ``exact`` only a byte-identical URL ``similar`` same site and page, ignoring fragment, trailing slash, query order, ``www.`` and default ports; a request for the site root also matches any tab on that site ``origin`` any tab on the same site, whatever its path """ from __future__ import annotations from dataclasses import dataclass from typing import Any, Literal from urllib.parse import parse_qsl, urlsplit ReuseMode = Literal["none", "exact", "similar", "origin"] REUSE_MODES: tuple[str, ...] = ("none", "exact", "similar", "origin") _DEFAULT_PORTS = {"http": 80, "https": 443, "ws": 80, "wss": 443} _ROOT_PATHS = {"", "/"} @dataclass(frozen=True) class UrlKey: """Canonical, comparable form of a URL.""" scheme: str host: str port: int | None path: str query: tuple[tuple[str, str], ...] @property def origin(self) -> tuple[str, str, int | None]: return (self.scheme, self.host, self.port) @property def is_root(self) -> bool: return self.path == "" and not self.query def url_key(url: str | None) -> UrlKey | None: """Return the canonical key of *url*, or ``None`` when it is not comparable.""" if not url: return None parts = urlsplit(url.strip()) if not parts.scheme or not parts.hostname: return None scheme = parts.scheme.lower() host = parts.hostname.lower() if host.startswith("www.") and len(host) > 4: host = host[4:] port = parts.port if port is not None and _DEFAULT_PORTS.get(scheme) == port: port = None path = parts.path.rstrip("/") query = tuple(sorted(parse_qsl(parts.query, keep_blank_values=True))) return UrlKey(scheme=scheme, host=host, port=port, path=path, query=query) def normalize_mode(reuse: bool | str | None, reuse_domain: bool = False) -> ReuseMode: """Fold the boolean flags and explicit mode names into one :data:`ReuseMode`.""" if reuse_domain: return "origin" if reuse is True: return "similar" if reuse is False or reuse is None: return "none" mode = str(reuse).lower() if mode not in REUSE_MODES: raise ValueError(f"Unknown reuse mode '{reuse}'. Use one of: {', '.join(REUSE_MODES)}") return mode # type: ignore[return-value] def urls_match(url: str, candidate: str | None, mode: ReuseMode) -> bool: """Report whether *candidate* may be reused for *url* under *mode*.""" if mode == "none" or not candidate: return False if mode == "exact": return candidate == url wanted, other = url_key(url), url_key(candidate) if wanted is None or other is None: return False if wanted.origin != other.origin: return False if mode == "origin": return True return wanted.path == other.path or wanted.is_root def needs_navigation(current: str | None, url: str) -> bool: """Report whether a reused tab still has to be navigated to *url*.""" if current == url: return False wanted, other = url_key(url), url_key(current) return wanted is None or other is None or wanted != other def tab_url(tab: Any) -> str | None: """Read a tab URL from an SDK ``Tab`` or a serialized tab dict.""" if isinstance(tab, dict): value = tab.get("url") else: value = getattr(tab, "url", None) return value if isinstance(value, str) else None def tab_id(tab: Any) -> int | None: """Read a tab ID from an SDK ``Tab`` or a serialized tab dict.""" value = tab.get("id") if isinstance(tab, dict) else getattr(tab, "id", None) return value if isinstance(value, int) else None def find_reusable_tab(tabs: Any, url: str, mode: ReuseMode): """Return the open tab that best fits *url*, or ``None``. Closer matches win: an identical URL beats a canonical match, which beats a looser site-level match; among equally loose matches the tab closest to the requested page wins. """ if mode == "none": return None wanted = url_key(url) best, best_rank = None, None for tab in tabs or (): candidate = tab_url(tab) if not urls_match(url, candidate, mode): continue rank = _rank(wanted, candidate) if best_rank is None or rank < best_rank: best, best_rank = tab, rank return best def _rank(wanted: UrlKey | None, candidate: str | None) -> tuple[int, int]: """Lower is a closer match: exact string, canonical, then shallowest path.""" other = url_key(candidate) if wanted is not None and other is not None and wanted == other: return (0, 0) depth = other.path.count("/") if other is not None else 99 return (1, depth)