mirror of
https://github.com/Hopiu/bowser.git
synced 2026-05-02 07:24:42 +00:00
- Introduced a new feature to visualize the Document Object Model (DOM) tree of the loaded web page. - Implemented keyboard shortcut (Ctrl+Shift+D) for generating and displaying the DOM graph. - Added core implementation files: - src/debug/dom_graph.py: Handles generation and rendering of graphs in DOT and SVG formats. - Created templates and documentation for the new feature, including: - docs/DOM_VISUALIZATION.md: Overview and usage instructions. - docs/DOM_GRAPH_UX.md: User experience design documentation. - Allowed the visualization to open in a new browser tab instead of an external viewer. - Enhanced visual representation through color coding of different HTML elements. - Implemented comprehensive tests for graph generation and page rendering. - Updated README.md to include usage instructions for DOM visualization. - Included test_holder.html as an example test page. - Modified various components in the browser to integrate tab management and enhance the graphical rendering experience.
127 lines
4.2 KiB
Python
127 lines
4.2 KiB
Python
"""Browser entry and orchestration."""
|
|
|
|
import gi
|
|
import logging
|
|
|
|
gi.require_version("Gtk", "4.0")
|
|
from gi.repository import Gtk
|
|
|
|
from ..network.url import URL
|
|
from .chrome import Chrome
|
|
from .tab import Tab
|
|
|
|
|
|
class Browser:
|
|
def __init__(self):
|
|
self.logger = logging.getLogger("bowser.browser")
|
|
self.app = Gtk.Application(application_id="ch.bowser.bowser")
|
|
self.app.connect("activate", self.on_activate)
|
|
|
|
self.tabs = []
|
|
self.active_tab: Tab | None = None
|
|
self.chrome = Chrome(self)
|
|
|
|
def _log(self, msg: str, level: int = logging.INFO):
|
|
self.logger.log(level, msg)
|
|
|
|
def on_activate(self, app):
|
|
"""Called when the application is activated."""
|
|
self._log("Application activated", logging.DEBUG)
|
|
self.chrome.create_window()
|
|
|
|
def new_tab(self, url: str):
|
|
tab = Tab(self)
|
|
# Normalize URL to ensure https:// protocol
|
|
url = self._normalize_url(url)
|
|
tab.load(URL(url))
|
|
self.tabs.append(tab)
|
|
self.active_tab = tab
|
|
# Add to UI
|
|
self.chrome.add_tab(tab)
|
|
self.chrome.update_tab(tab) # Update title after load
|
|
self.chrome.update_address_bar()
|
|
self._log(f"New tab opened: {url}", logging.INFO)
|
|
return tab
|
|
|
|
def set_active_tab(self, tab: Tab, *_args):
|
|
if tab in self.tabs:
|
|
self.active_tab = tab
|
|
self._log(f"Active tab set: {tab.title}", logging.DEBUG)
|
|
# Update UI
|
|
self.chrome.set_active_tab(tab)
|
|
# Trigger repaint of content area
|
|
self.chrome.paint()
|
|
self.chrome.update_address_bar()
|
|
|
|
def close_tab(self, tab: Tab, *_args):
|
|
if tab not in self.tabs:
|
|
return
|
|
idx = self.tabs.index(tab)
|
|
self.tabs.remove(tab)
|
|
# Choose new active tab if needed
|
|
if self.active_tab is tab:
|
|
if self.tabs:
|
|
self.active_tab = self.tabs[max(0, idx - 1)]
|
|
self.chrome.set_active_tab(self.active_tab)
|
|
else:
|
|
self.active_tab = None
|
|
# Open a new tab when all tabs are closed
|
|
self.new_tab("about:startpage")
|
|
return # Return early - don't try to remove a tab that's being closed
|
|
# Update UI (tab_pages already cleaned in _on_close_page)
|
|
self.chrome.paint()
|
|
self.chrome.update_address_bar()
|
|
self._log(f"Tab closed: {tab.title}", logging.INFO)
|
|
|
|
# Navigation and history wrappers
|
|
def navigate_to(self, url_str: str):
|
|
if not url_str:
|
|
return
|
|
# Add https:// if no protocol provided
|
|
url_str = self._normalize_url(url_str)
|
|
if not self.active_tab:
|
|
self.new_tab(url_str)
|
|
return
|
|
self.active_tab.load(URL(url_str))
|
|
self.chrome.paint()
|
|
self.chrome.update_tab(self.active_tab) # Update title after load
|
|
self.chrome.update_address_bar()
|
|
self._log(f"Navigate to: {url_str}", logging.INFO)
|
|
|
|
def _normalize_url(self, url_str: str) -> str:
|
|
"""Add https:// protocol if not present."""
|
|
url_str = url_str.strip()
|
|
# If URL already has a protocol, return as-is
|
|
if "://" in url_str:
|
|
return url_str
|
|
# Special about: URLs
|
|
if url_str.startswith("about:"):
|
|
return url_str
|
|
# Otherwise, assume https://
|
|
return f"https://{url_str}"
|
|
|
|
def go_back(self):
|
|
if self.active_tab and self.active_tab.go_back():
|
|
self.chrome.paint()
|
|
self.chrome.update_tab(self.active_tab)
|
|
self.chrome.update_address_bar()
|
|
self._log("Go back", logging.DEBUG)
|
|
|
|
def go_forward(self):
|
|
if self.active_tab and self.active_tab.go_forward():
|
|
self.chrome.paint()
|
|
self.chrome.update_tab(self.active_tab)
|
|
self.chrome.update_address_bar()
|
|
self._log("Go forward", logging.DEBUG)
|
|
|
|
def reload(self):
|
|
if self.active_tab:
|
|
self.active_tab.reload()
|
|
self.chrome.paint()
|
|
self.chrome.update_tab(self.active_tab)
|
|
self.chrome.update_address_bar()
|
|
self._log("Reload", logging.DEBUG)
|
|
|
|
def run(self):
|
|
"""Start the GTK application main loop."""
|
|
return self.app.run()
|