QtBrowser splitted

parent 7f478654
import sys, os
sys.path.append(os.path.abspath(os.path.dirname(__file__)))
print(sys.path)
...@@ -2,52 +2,38 @@ ...@@ -2,52 +2,38 @@
import os import os
import sys import sys
import subprocess
import tempfile
import json import json
import shutil
import asyncio import asyncio
import platform import platform
import signal import signal
from pathlib import Path
from typing import Dict, List, Optional, Union, Callable, Any
import configparser import configparser
from functools import partial from functools import partial
# Import our RuntimeBridge for chrome.runtime API emulation # Import our RuntimeBridge for chrome.runtime API emulation
from assets.browser.js.runtime_bridge import RuntimeBridge, create_runtime_api_script from shmcs.qtbrowser.runtime_bridge import RuntimeBridge, create_runtime_api_script
import re
from PyQt6.QtCore import Qt, QUrl, QProcess, pyqtSignal, QSize, QRect, QTimer, QMimeData, QPoint, QByteArray, QBuffer from PyQt6.QtCore import Qt, QUrl, QSize, QPoint
from PyQt6.QtWebChannel import QWebChannel from PyQt6.QtWebChannel import QWebChannel
from PyQt6.QtGui import QIcon, QAction, QKeySequence, QFont, QColor, QPainter, QDrag, QPixmap from PyQt6.QtGui import QIcon, QAction, QKeySequence, QFont, QColor, QPainter
from PyQt6.QtWidgets import ( from PyQt6.QtWidgets import (
QApplication, QMainWindow, QTabWidget, QToolBar, QLineEdit, QApplication, QMainWindow, QTabWidget, QToolBar, QLineEdit,
QPushButton, QWidget, QVBoxLayout, QHBoxLayout, QDialog, QPushButton, QWidget, QVBoxLayout, QHBoxLayout, QDialog,
QLabel, QListWidget, QListWidgetItem, QCheckBox, QMenu, QLabel, QMenu, QStyle, QMessageBox, QStatusBar,
QSizePolicy, QStyle, QFrame, QSplitter, QMessageBox, QStatusBar, QTabBar, QStyleOptionTab, QFileDialog,
QInputDialog, QTextEdit QInputDialog, QTextEdit
) )
from PyQt6.QtWebEngineWidgets import QWebEngineView from PyQt6.QtWebEngineWidgets import QWebEngineView
from PyQt6.QtWebEngineCore import ( from PyQt6.QtWebEngineCore import (
QWebEngineProfile, QWebEngineSettings, QWebEnginePage, QWebEngineUrlSchemeHandler, QWebEngineProfile, QWebEngineSettings, QWebEnginePage, QWebEngineUrlScheme,
QWebEngineUrlRequestJob, QWebEngineUrlRequestInterceptor, QWebEngineUrlScheme,
QWebEngineScript QWebEngineScript
) )
import mimetypes
# Import Playwright for API compatibility # Import Playwright for API compatibility
from playwright.async_api import async_playwright, Browser as PlaywrightBrowser from playwright.async_api import async_playwright
from playwright.async_api import Page as PlaywrightPage
from playwright.async_api import BrowserContext as PlaywrightBrowserContext
from shmcs.qtbrowser.tabs import BrowserTab, DetachableTabBar
from page import ChromeWebEnginePage from shmcs.qtbrowser.chrome import ChromeUrlInterceptor, ChromeWebEnginePage
from tabs import BrowserTab, DetachableTabBar from shmcs.qtbrowser.extensions import ExtensionSchemeHandler, ExtensionDialog, ContentScriptInjector
from injector import ContentScriptInjector from shmcs.qtbrowser.profiles import ProfileDialog
from chrome import ChromeUrlInterceptor
from extensions import ExtensionSchemeHandler, ExtensionDialog
from profiles import ProfileDialog
# Register URL schemes before any QApplication is created # Register URL schemes before any QApplication is created
def register_url_schemes(): def register_url_schemes():
...@@ -1633,191 +1619,7 @@ class Browser(QMainWindow): ...@@ -1633,191 +1619,7 @@ class Browser(QMainWindow):
dialog.exec() dialog.exec()
class QPlaywrightBrowser:
"""
A class that wraps the Playwright browser API and uses Qt6Browser internally.
This class exposes the Playwright API while using the Qt6 browser for display.
"""
def __init__(self):
self.app = QApplication.instance() or QApplication(sys.argv)
self.browser_ui = None
self.playwright = None
self.browser = None
self.contexts = {}
self.event_loop = None
async def launch(self, **kwargs):
"""Launch a new browser instance."""
# Initialize Playwright if not already done
if not self.playwright:
self.playwright = await async_playwright().start()
# Create the Qt browser UI
if not self.browser_ui:
self.browser_ui = Browser()
self.browser_ui.show()
# Launch the actual Playwright browser (hidden)
# We'll use this for API compatibility but display in Qt
self.browser = await self.playwright.chromium.launch(
headless=True, # Run headless since we're displaying in Qt
args = [
'--headless=new',
'--enable-features=Autofill',
]+os.environ.get('QTWEBENGINE_CHROMIUM_FLAGS').split(),
**kwargs
)
#
# Create a default context
default_context = await self.browser.new_context()
self.contexts["default"] = default_context
return self
async def new_context(self, **kwargs):
"""Create a new browser context."""
if not self.browser:
await self.launch()
# Create a new context in the Playwright browser
context_id = f"context_{len(self.contexts)}"
context = await self.browser.new_context(**kwargs)
self.contexts[context_id] = context
# Return a wrapper that provides both Playwright API and Qt UI
return QPlaywrightBrowserContext(self, context, context_id)
async def new_page(self, url="about:blank"):
"""Create a new page in the default context."""
if "default" not in self.contexts:
await self.launch()
# Create a new page in the Playwright browser
pw_page = await self.contexts["default"].new_page()
await pw_page.goto(url)
# Create a new tab in the Qt browser
qt_tab = self.browser_ui.new_page(url)
qt_tab.page = pw_page
# Return a wrapper that provides both Playwright API and Qt UI
return QPlaywrightPage(pw_page, qt_tab)
async def close(self):
"""Close the browser."""
# Close all Playwright contexts and browser
if self.browser:
for context_id, context in self.contexts.items():
await context.close()
await self.browser.close()
self.contexts = {}
self.browser = None
# Close the Qt browser UI
if self.browser_ui:
self.browser_ui.close()
self.browser_ui = None
# Close Playwright
if self.playwright:
await self.playwright.stop()
self.playwright = None
def run(self):
"""Run the application event loop."""
return self.app.exec()
def run_async(self, coro):
"""Run an async coroutine in the Qt event loop."""
if not self.event_loop:
self.event_loop = asyncio.new_event_loop()
asyncio.set_event_loop(self.event_loop)
# Create a QTimer to process asyncio events
timer = QTimer()
timer.timeout.connect(lambda: self.event_loop.run_until_complete(asyncio.sleep(0)))
timer.start(10) # 10ms interval
# Run the coroutine
future = asyncio.run_coroutine_threadsafe(coro, self.event_loop)
return future
class QPlaywrightBrowserContext:
"""
A wrapper around a Playwright BrowserContext that provides both
Playwright API and Qt UI integration.
"""
def __init__(self, qbrowser, context, context_id):
self.qbrowser = qbrowser
self.context = context
self.context_id = context_id
self.pages = []
async def new_page(self):
"""Create a new page in this context."""
# Create a new page in the Playwright context
pw_page = await self.context.new_page()
# Create a new tab in the Qt browser
qt_tab = self.qbrowser.browser_ui.new_page("about:blank")
qt_tab.page = pw_page
# Create a wrapper page
page = QPlaywrightPage(pw_page, qt_tab)
self.pages.append(page)
return page
async def close(self):
"""Close this context."""
# Close all pages
for page in self.pages:
await page.close()
# Close the Playwright context
await self.context.close()
# Remove from the qbrowser contexts
if self.context_id in self.qbrowser.contexts:
del self.qbrowser.contexts[self.context_id]
class QPlaywrightPage:
"""
A wrapper around a Playwright Page that provides both
Playwright API and Qt UI integration.
"""
def __init__(self, pw_page, qt_tab):
self.pw_page = pw_page
self.qt_tab = qt_tab
# Forward Playwright page methods and properties
self.goto = pw_page.goto
self.click = pw_page.click
self.fill = pw_page.fill
self.type = pw_page.type
self.press = pw_page.press
self.wait_for_selector = pw_page.wait_for_selector
self.wait_for_navigation = pw_page.wait_for_navigation
self.wait_for_load_state = pw_page.wait_for_load_state
self.evaluate = pw_page.evaluate
self.screenshot = pw_page.screenshot
self.content = pw_page.content
self.title = pw_page.title
self.url = pw_page.url
async def close(self):
"""Close this page."""
# Close the Playwright page
await self.pw_page.close()
# Close the Qt tab
# We need to use the Qt event loop for this
index = self.qt_tab.parent.indexOf(self.qt_tab)
if index >= 0:
self.qt_tab.parent.close_tab(index)
async def main_async(): async def main_async():
......
This source diff could not be displayed because it is too large. You can view the blob instead.
This diff is collapsed.
This source diff could not be displayed because it is too large. You can view the blob instead.
This source diff could not be displayed because it is too large. You can view the blob instead.
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
qbrowser.py - A Qt6-based browser that wraps and extends the Playwright browser class
This module provides a Qt6-based browser interface that wraps the Playwright browser
API and launches Chromium browser instances without decorations using the --app flag.
Each new page is opened as a new tab in the Qt6 window, and the interface includes
URL input, home button, and browser extension management.
"""
import os
import sys
import subprocess
import tempfile
import json
import shutil
import asyncio
import platform
import signal
from pathlib import Path
from typing import Dict, List, Optional, Union, Callable, Any
import configparser
from functools import partial
import re
from PyQt6.QtCore import Qt, QUrl, QProcess, pyqtSignal, QSize, QRect, QTimer, QMimeData, QPoint, QByteArray, QBuffer
from PyQt6.QtWebChannel import QWebChannel
from PyQt6.QtGui import QIcon, QAction, QKeySequence, QFont, QColor, QPainter, QDrag, QPixmap
from PyQt6.QtWidgets import (
QApplication, QMainWindow, QTabWidget, QToolBar, QLineEdit,
QPushButton, QWidget, QVBoxLayout, QHBoxLayout, QDialog,
QLabel, QListWidget, QListWidgetItem, QCheckBox, QMenu,
QSizePolicy, QStyle, QFrame, QSplitter, QMessageBox, QStatusBar, QTabBar, QStyleOptionTab, QFileDialog,
QInputDialog, QTextEdit
)
from PyQt6.QtWebEngineWidgets import QWebEngineView
from PyQt6.QtWebEngineCore import (
QWebEngineProfile, QWebEngineSettings, QWebEnginePage, QWebEngineUrlSchemeHandler,
QWebEngineUrlRequestJob, QWebEngineUrlRequestInterceptor, QWebEngineUrlScheme,
QWebEngineScript
)
import mimetypes
# Import Playwright for API compatibility
from playwright.async_api import async_playwright, Browser as PlaywrightBrowser
from playwright.async_api import Page as PlaywrightPage
from playwright.async_api import BrowserContext as PlaywrightBrowserContext
class QPlaywrightBrowser:
"""
A class that wraps the Playwright browser API and uses Qt6Browser internally.
This class exposes the Playwright API while using the Qt6 browser for display.
"""
def __init__(self):
self.app = QApplication.instance() or QApplication(sys.argv)
self.browser_ui = None
self.playwright = None
self.browser = None
self.contexts = {}
self.event_loop = None
async def launch(self, **kwargs):
"""Launch a new browser instance."""
# Initialize Playwright if not already done
if not self.playwright:
self.playwright = await async_playwright().start()
# Create the Qt browser UI
if not self.browser_ui:
self.browser_ui = Browser()
self.browser_ui.show()
# Launch the actual Playwright browser (hidden)
# We'll use this for API compatibility but display in Qt
self.browser = await self.playwright.chromium.launch(
headless=True, # Run headless since we're displaying in Qt
args = [
'--headless=new',
'--enable-features=Autofill',
]+os.environ.get('QTWEBENGINE_CHROMIUM_FLAGS').split(),
**kwargs
)
#
# Create a default context
default_context = await self.browser.new_context()
self.contexts["default"] = default_context
return self
async def new_context(self, **kwargs):
"""Create a new browser context."""
if not self.browser:
await self.launch()
# Create a new context in the Playwright browser
context_id = f"context_{len(self.contexts)}"
context = await self.browser.new_context(**kwargs)
self.contexts[context_id] = context
# Return a wrapper that provides both Playwright API and Qt UI
return QPlaywrightBrowserContext(self, context, context_id)
async def new_page(self, url="about:blank"):
"""Create a new page in the default context."""
if "default" not in self.contexts:
await self.launch()
# Create a new page in the Playwright browser
pw_page = await self.contexts["default"].new_page()
await pw_page.goto(url)
# Create a new tab in the Qt browser
qt_tab = self.browser_ui.new_page(url)
qt_tab.page = pw_page
# Return a wrapper that provides both Playwright API and Qt UI
return QPlaywrightPage(pw_page, qt_tab)
async def close(self):
"""Close the browser."""
# Close all Playwright contexts and browser
if self.browser:
for context_id, context in self.contexts.items():
await context.close()
await self.browser.close()
self.contexts = {}
self.browser = None
# Close the Qt browser UI
if self.browser_ui:
self.browser_ui.close()
self.browser_ui = None
# Close Playwright
if self.playwright:
await self.playwright.stop()
self.playwright = None
def run(self):
"""Run the application event loop."""
return self.app.exec()
def run_async(self, coro):
"""Run an async coroutine in the Qt event loop."""
if not self.event_loop:
self.event_loop = asyncio.new_event_loop()
asyncio.set_event_loop(self.event_loop)
# Create a QTimer to process asyncio events
timer = QTimer()
timer.timeout.connect(lambda: self.event_loop.run_until_complete(asyncio.sleep(0)))
timer.start(10) # 10ms interval
# Run the coroutine
future = asyncio.run_coroutine_threadsafe(coro, self.event_loop)
return future
class QPlaywrightBrowserContext:
"""
A wrapper around a Playwright BrowserContext that provides both
Playwright API and Qt UI integration.
"""
def __init__(self, qbrowser, context, context_id):
self.qbrowser = qbrowser
self.context = context
self.context_id = context_id
self.pages = []
async def new_page(self):
"""Create a new page in this context."""
# Create a new page in the Playwright context
pw_page = await self.context.new_page()
# Create a new tab in the Qt browser
qt_tab = self.qbrowser.browser_ui.new_page("about:blank")
qt_tab.page = pw_page
# Create a wrapper page
page = QPlaywrightPage(pw_page, qt_tab)
self.pages.append(page)
return page
async def close(self):
"""Close this context."""
# Close all pages
for page in self.pages:
await page.close()
# Close the Playwright context
await self.context.close()
# Remove from the qbrowser contexts
if self.context_id in self.qbrowser.contexts:
del self.qbrowser.contexts[self.context_id]
class QPlaywrightPage:
"""
A wrapper around a Playwright Page that provides both
Playwright API and Qt UI integration.
"""
def __init__(self, pw_page, qt_tab):
self.pw_page = pw_page
self.qt_tab = qt_tab
# Forward Playwright page methods and properties
self.goto = pw_page.goto
self.click = pw_page.click
self.fill = pw_page.fill
self.type = pw_page.type
self.press = pw_page.press
self.wait_for_selector = pw_page.wait_for_selector
self.wait_for_navigation = pw_page.wait_for_navigation
self.wait_for_load_state = pw_page.wait_for_load_state
self.evaluate = pw_page.evaluate
self.screenshot = pw_page.screenshot
self.content = pw_page.content
self.title = pw_page.title
self.url = pw_page.url
async def close(self):
"""Close this page."""
# Close the Playwright page
await self.pw_page.close()
# Close the Qt tab
# We need to use the Qt event loop for this
index = self.qt_tab.parent.indexOf(self.qt_tab)
if index >= 0:
self.qt_tab.parent.close_tab(index)
This diff is collapsed.
This diff is collapsed.
This source diff could not be displayed because it is too large. You can view the blob instead.
This diff is collapsed.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment