Inject bridge script when profile set-up skipped

Some add-ons fully override AnkiWebPage.__init__ and thus depend on _setupBridge injecting the JS bridge script.

With this change we account for these cases, while giving add-ons the opportunity to look for solutions that do not require overriding AnkiWebPage.__init__ completely.

(cherry picked from commit 2a97b135ee)
This commit is contained in:
Aristotelis P 2025-04-15 17:46:19 +02:00 committed by Damien Elmes
parent 0467f717ad
commit 269fb073e9

View file

@ -73,44 +73,7 @@ class AuthInterceptor(QWebEngineUrlRequestInterceptor):
info.setHttpHeader(b"Authorization", f"Bearer {_APIKEY}".encode("utf-8"))
_profile_with_api_access: QWebEngineProfile | None = None
_profile_without_api_access: QWebEngineProfile | None = None
class AnkiWebPage(QWebEnginePage):
def __init__(
self,
onBridgeCmd: BridgeCommandHandler,
kind: AnkiWebViewKind = AnkiWebViewKind.DEFAULT,
parent: QObject | None = None,
) -> None:
profile = self._profileForPage(kind)
QWebEnginePage.__init__(self, profile, parent)
self._onBridgeCmd = onBridgeCmd
self._kind = kind
self._setupBridge()
self.open_links_externally = True
def _profileForPage(self, kind: AnkiWebViewKind) -> QWebEngineProfile:
have_api_access = kind in (
AnkiWebViewKind.DECK_OPTIONS,
AnkiWebViewKind.EDITOR,
AnkiWebViewKind.DECK_STATS,
AnkiWebViewKind.CHANGE_NOTETYPE,
AnkiWebViewKind.BROWSER_CARD_INFO,
)
global _profile_with_api_access, _profile_without_api_access
# Use cached profile if available
if have_api_access and _profile_with_api_access is not None:
return _profile_with_api_access
elif not have_api_access and _profile_without_api_access is not None:
return _profile_without_api_access
# Create a new profile if not cached
profile = QWebEngineProfile()
def _create_bridge_script() -> QWebEngineScript:
qwebchannel = ":/qtwebchannel/qwebchannel.js"
jsfile = QFile(qwebchannel)
if not jsfile.open(QIODevice.OpenModeFlag.ReadOnly):
@ -143,9 +106,49 @@ class AnkiWebPage(QWebEnginePage):
script.setInjectionPoint(QWebEngineScript.InjectionPoint.DocumentReady)
script.setRunsOnSubFrames(False)
scripts = profile.scripts()
assert scripts is not None
scripts.insert(script)
return script
_bridge_script = _create_bridge_script()
_profile_with_api_access: QWebEngineProfile | None = None
_profile_without_api_access: QWebEngineProfile | None = None
class AnkiWebPage(QWebEnginePage):
def __init__(
self,
onBridgeCmd: BridgeCommandHandler,
kind: AnkiWebViewKind = AnkiWebViewKind.DEFAULT,
parent: QObject | None = None,
) -> None:
profile = self._profileForPage(kind)
self._inject_user_script(profile, _bridge_script)
QWebEnginePage.__init__(self, profile, parent)
self._onBridgeCmd = onBridgeCmd
self._kind = kind
self._setupBridge()
self.open_links_externally = True
def _profileForPage(self, kind: AnkiWebViewKind) -> QWebEngineProfile:
have_api_access = kind in (
AnkiWebViewKind.DECK_OPTIONS,
AnkiWebViewKind.EDITOR,
AnkiWebViewKind.DECK_STATS,
AnkiWebViewKind.CHANGE_NOTETYPE,
AnkiWebViewKind.BROWSER_CARD_INFO,
)
global _profile_with_api_access, _profile_without_api_access
# Use cached profile if available
if have_api_access and _profile_with_api_access is not None:
return _profile_with_api_access
elif not have_api_access and _profile_without_api_access is not None:
return _profile_without_api_access
# Create a new profile if not cached
profile = QWebEngineProfile()
interceptor = AuthInterceptor(profile, api_enabled=have_api_access)
profile.setUrlRequestInterceptor(interceptor)
@ -157,6 +160,19 @@ class AnkiWebPage(QWebEnginePage):
return profile
def _setupBridge(self) -> None:
# Add-on compatibility: For existing add-on callers that override the init
# and invoke _setupBridge directly (e.g. in order to use a custom web profile),
# we need to ensure that the bridge script is injected into the profile scripts,
# if it has yet to be injected.
profile = self.profile()
assert profile is not None
scripts = profile.scripts()
assert scripts is not None
if not scripts.contains(_bridge_script):
print("add-on callers should not call _setupBridge directly")
self._inject_user_script(profile, _bridge_script)
class Bridge(QObject):
def __init__(self, bridge_handler: Callable[[str], Any]) -> None:
super().__init__()
@ -172,6 +188,13 @@ class AnkiWebPage(QWebEnginePage):
self._channel.registerObject("py", self._bridge)
self.setWebChannel(self._channel)
def _inject_user_script(
self, profile: QWebEngineProfile, script: QWebEngineScript
) -> None:
scripts = profile.scripts()
assert scripts is not None
scripts.insert(script)
def javaScriptConsoleMessage(
self,
level: QWebEnginePage.JavaScriptConsoleMessageLevel,