"""Navigation namespace: ``b.nav.*``.""" from __future__ import annotations from browser_cli.models import Tab from browser_cli.sdk.base import Namespace, sdk_command def _open_args(self, url, *, background=False, focus=False, window=None, group=None, **_ignored): return {"url": url, "background": background or not focus, "focus": focus, "window": window, "group": group} def _tab_args(self, tab_id=None): return {"tabId": tab_id} class NavigationNS(Namespace): """Open URLs, navigate history, and focus tabs.""" def open( self, url: str, *, background: bool = False, focus: bool = False, window: str | None = None, group: str | None = None, reuse: bool = False, reuse_domain: bool = False, reuse_title: str | None = None, ) -> None: """Open *url* in a new tab without stealing OS focus by default. ``reuse``/``reuse_domain``/``reuse_title`` navigate an existing matching tab instead of creating a new one. """ tab = self._reuse_target(url, reuse=reuse, reuse_domain=reuse_domain, reuse_title=reuse_title) if tab is not None: self.to(tab.id, url) if focus: self._c.tabs.activate(tab.id) return None self.command("navigate.open", _open_args(self, url, background=background, focus=focus, window=window, group=group)) return None def open_wait( self, url: str, *, timeout: float = 30.0, background: bool = False, focus: bool = False, window: str | None = None, group: str | None = None, reuse: bool = False, reuse_domain: bool = False, reuse_title: str | None = None, ) -> Tab: """Open *url* in a new tab and block until fully loaded. Returns the Tab.""" tab = self._reuse_target(url, reuse=reuse, reuse_domain=reuse_domain, reuse_title=reuse_title) if tab is not None: self.to(tab.id, url) if focus: self._c.tabs.activate(tab.id) return self._c.tabs.wait_for_load(tab.id, timeout=timeout) return self.require_tab( self.command("navigate.open_wait", { "url": url, "timeout": int(timeout * 1000), "background": background or not focus, "focus": focus, "window": window, "group": group, }), "navigate.open_wait returned unexpected data", ) @sdk_command("navigate.reload", _tab_args) def reload(self, tab_id: int | None = None) -> None: """Reload the active tab or a specific tab.""" @sdk_command("navigate.hard_reload", _tab_args) def hard_reload(self, tab_id: int | None = None) -> None: """Hard-reload the active tab or a specific tab.""" @sdk_command("navigate.back", _tab_args) def back(self, tab_id: int | None = None) -> None: """Navigate back in the active tab or a specific tab.""" @sdk_command("navigate.forward", _tab_args) def forward(self, tab_id: int | None = None) -> None: """Navigate forward in the active tab or a specific tab.""" @sdk_command("navigate.focus", lambda self, pattern: {"pattern": pattern}) def focus(self, pattern: str) -> dict | None: """Focus the first tab whose URL matches *pattern*. Returns the matched tab info, if any.""" @sdk_command("navigate.to", lambda self, tab_id, url: {"tabId": tab_id, "url": url}) def to(self, tab_id: int, url: str) -> None: """Navigate a specific tab to *url* in place.""" def _reuse_target(self, url: str, *, reuse: bool, reuse_domain: bool, reuse_title: str | None): if not (reuse or reuse_domain or reuse_title): return None from urllib.parse import urlparse wanted = urlparse(url) wanted_host = wanted.netloc.lower() for tab in self._c.tabs.list(): tab_url = tab.url or "" parsed = urlparse(tab_url) if reuse and tab_url == url: return tab if reuse_domain and wanted_host and parsed.netloc.lower() == wanted_host: return tab if reuse_title and reuse_title.lower() in (tab.title or "").lower(): return tab return None def search( self, engine: str, query: str, *, background: bool = False, focus: bool = False, window: str | None = None, group: str | None = None, ) -> None: """Open a search query in the given engine (e.g. 'google', 'youtube', 'ddg').""" from urllib.parse import quote_plus from browser_cli.search.engines import ENGINES template = ENGINES.get(engine) if template is None: raise ValueError(f"Unknown search engine '{engine}'. Available: {', '.join(ENGINES)}") url = template.format(query=quote_plus(query)) self.command("navigate.open", {"url": url, "background": background or not focus, "focus": focus, "window": window, "group": group})