version: Add basic info about loaded WebExtensions

This commit is contained in:
Florian Bruhin 2025-10-23 23:24:40 +02:00
parent 6f9cf43d6d
commit 242cf2a22e
2 changed files with 203 additions and 9 deletions

View File

@ -925,6 +925,46 @@ def _backend() -> str:
raise utils.Unreachable(objects.backend)
def _webengine_extensions() -> Sequence[str]:
"""Get a list of WebExtensions enabled in QtWebEngine."""
lines: list[str] = []
if (
objects.backend == usertypes.Backend.QtWebEngine
and "avoid-chromium-init" not in objects.debug_flags
and machinery.IS_QT6 # mypy; TODO early return once Qt 5 is dropped
):
from qutebrowser.qt.webenginecore import QWebEngineProfile
profile = QWebEngineProfile.defaultProfile()
assert profile is not None # mypy
try:
ext_manager = profile.extensionManager()
except AttributeError:
# Added in QtWebEngine 6.10
return []
assert ext_manager is not None # mypy
lines.append("WebExtensions:")
if not ext_manager.extensions():
lines[0] += " none"
for info in ext_manager.extensions():
tags = [
("[x]" if info.isEnabled() else "[ ]") + " enabled",
("[x]" if info.isLoaded() else "[ ]") + " loaded",
("[x]" if info.isInstalled() else "[ ]") + " installed",
]
lines.append(f" {info.name()} ({info.id()})")
lines.append(f" {' '.join(tags)}")
lines.append(f" {info.path()}")
url = info.actionPopupUrl()
if url.isValid():
lines.append(f" {url.toDisplayString()}")
lines.append("")
return lines
def _uptime() -> datetime.timedelta:
time_delta = datetime.datetime.now() - objects.qapp.launch_time
# Round off microseconds
@ -974,6 +1014,8 @@ def version_info() -> str:
if QSslSocket.supportsSsl() else 'no'),
]
lines += _webengine_extensions()
if objects.qapp:
style = objects.qapp.style()
assert style is not None

View File

@ -23,7 +23,8 @@ import pytest_mock
import hypothesis
import hypothesis.strategies
from qutebrowser.qt import machinery
from qutebrowser.qt.core import PYQT_VERSION_STR
from qutebrowser.qt.core import PYQT_VERSION_STR, QUrl
from qutebrowser.qt.webenginecore import QWebEngineProfile
import qutebrowser
from qutebrowser.config import config, websettings
@ -1153,13 +1154,7 @@ class TestChromiumVersion:
def test_prefers_saved_user_agent(self, monkeypatch, patch_no_api):
webenginesettings._init_user_agent_str(_QTWE_USER_AGENT.format('87'))
class FakeProfile:
def defaultProfile(self):
raise AssertionError("Should not be called")
monkeypatch.setattr(webenginesettings, 'QWebEngineProfile', FakeProfile())
monkeypatch.setattr(QWebEngineProfile, "defaultProfile", lambda: 1/0)
version.qtwebengine_versions()
def test_unpatched(self, qapp, cache_tmpdir, data_tmpdir, config_stub):
@ -1280,6 +1275,62 @@ class TestChromiumVersion:
assert versions.webengine == override
class FakeExtensionInfo:
def __init__(
self,
name: str,
*,
enabled: bool = False,
installed: bool = False,
loaded: bool = False,
action_popup_url: QUrl = QUrl(),
) -> None:
self._name = name
self.enabled = enabled
self.installed = installed
self.loaded = loaded
self.action_popup_url = action_popup_url
def isEnabled(self) -> bool:
return self.enabled
def isInstalled(self) -> bool:
return self.installed
def isLoaded(self) -> bool:
return self.loaded
def name(self) -> str:
return self._name
def actionPopupUrl(self) -> QUrl:
return self.action_popup_url
def path(self) -> str:
return f"{self._name}-path"
def id(self) -> str:
return f"{self._name}-id"
class FakeExtensionManager:
def __init__(self, extensions: list[FakeExtensionInfo]) -> None:
self._extensions = extensions
def extensions(self) -> list[FakeExtensionInfo]:
return self._extensions
class FakeExtensionProfile:
def __init__(self, ext_manager: FakeExtensionManager) -> None:
self._ext_manager = ext_manager
def extensionManager(self) -> FakeExtensionManager:
return self._ext_manager
@dataclasses.dataclass
class VersionParams:
@ -1373,6 +1424,7 @@ def test_version_info(params, stubs, monkeypatch, config_stub):
'python_path': 'EXECUTABLE PATH',
'uptime': "1:23:45",
'autoconfig_loaded': "yes" if params.autoconfig_loaded else "no",
'webextensions': "", # overridden below if QtWebEngine is used
}
patches['qtwebengine_versions'] = (
@ -1395,6 +1447,20 @@ def test_version_info(params, stubs, monkeypatch, config_stub):
substitutions['backend'] = 'new QtWebKit (WebKit WEBKIT VERSION)'
else:
monkeypatch.delattr(version, 'qtutils.qWebKitVersion', raising=False)
monkeypatch.setattr(
QWebEngineProfile,
"defaultProfile",
lambda: FakeExtensionProfile(
FakeExtensionManager([FakeExtensionInfo("ext1")])
),
)
substitutions['webextensions'] = (
"\n"
"WebExtensions:\n"
" ext1 (ext1-id)\n"
" [ ] enabled [ ] loaded [ ] installed\n"
" ext1-path\n"
)
patches['objects.backend'] = usertypes.Backend.QtWebEngine
substitutions['backend'] = 'QtWebEngine 1.2.3\n (source: faked)'
@ -1434,7 +1500,7 @@ def test_version_info(params, stubs, monkeypatch, config_stub):
pdf.js: PDFJS VERSION
sqlite: SQLITE VERSION
QtNetwork SSL: {ssl}
{style}{platform_plugin}{opengl}
{webextensions}{style}{platform_plugin}{opengl}
Platform: PLATFORM, ARCHITECTURE{linuxdist}
Frozen: {frozen}
Imported from {import_path}
@ -1519,6 +1585,92 @@ class TestOpenGLInfo:
assert str(info) == 'OpenGL ES'
class TestWebEngineExtensions:
def test_qtwebkit(self, monkeypatch: pytest.MonkeyPatch) -> None:
monkeypatch.setattr(version.objects, "backend", usertypes.Backend.QtWebKit)
monkeypatch.setattr(QWebEngineProfile, "defaultProfile", lambda: 1/0)
assert not version._webengine_extensions()
def test_avoid_chromium_init(self, monkeypatch: pytest.MonkeyPatch) -> None:
monkeypatch.setattr(version.objects, "backend", usertypes.Backend.QtWebEngine)
monkeypatch.setattr(objects, "debug_flags", {"avoid-chromium-init"})
monkeypatch.setattr(QWebEngineProfile, "defaultProfile", lambda: 1/0)
assert not version._webengine_extensions()
def test_no_extension_manager(self, monkeypatch: pytest.MonkeyPatch) -> None:
monkeypatch.setattr(QWebEngineProfile, "defaultProfile", object)
assert not version._webengine_extensions()
@pytest.mark.parametrize(
"extensions, expected",
[
pytest.param([], ["WebExtensions: none"], id="empty"),
pytest.param(
[FakeExtensionInfo("ext1")],
[
"WebExtensions:",
" ext1 (ext1-id)",
" [ ] enabled [ ] loaded [ ] installed",
" ext1-path",
"",
],
id="single",
),
pytest.param(
[
FakeExtensionInfo("ext1", enabled=True),
FakeExtensionInfo(
"ext2", enabled=True, loaded=True, installed=True
),
],
[
"WebExtensions:",
" ext1 (ext1-id)",
" [x] enabled [ ] loaded [ ] installed",
" ext1-path",
"",
" ext2 (ext2-id)",
" [x] enabled [x] loaded [x] installed",
" ext2-path",
"",
],
id="multiple",
),
pytest.param(
[
FakeExtensionInfo(
"ext", action_popup_url=QUrl("chrome-extension://ext")
)
],
[
"WebExtensions:",
" ext (ext-id)",
" [ ] enabled [ ] loaded [ ] installed",
" ext-path",
" chrome-extension://ext",
"",
],
id="with-url",
),
],
)
def test_extensions(
self,
monkeypatch: pytest.MonkeyPatch,
extensions: list[FakeExtensionInfo],
expected: list[str],
) -> None:
monkeypatch.setattr(
QWebEngineProfile,
"defaultProfile",
lambda: FakeExtensionProfile(
FakeExtensionManager(extensions)
),
)
assert version._webengine_extensions() == expected
@pytest.fixture
def pbclient(stubs):
http_stub = stubs.HTTPPostStub()