Anki is a smart spaced repetition flashcard program
Find a file
Damien Elmes db5117ce1a fix webviews sometimes failing to load, by enabling cache
Fixes #983

This has been a long-standing issue that was infrequent enough on
developer machines that we weren't able to get to the bottom of it before
now. As luck would have it, the new ARM build had just the right timing
for this to occur every few invocations, and I was able to narrow it down
to the call that turns off the cache.

We don't really want the cache, so this is not a great solution. But I
ran into trouble when trying to figure out a better solution:

- Calling setHttpCacheType() earlier (eg immediately after creating the
page) fails.
- It even fails if you attempt to change the setting in the shared
default profile before any webviews are loaded:

```
    def setupMainWindow(self) -> None:
        QWebEngineProfile.defaultProfile().setHttpCacheType(QWebEngineProfile.HttpCacheType.NoCache)
```

- Creating a profile separately, and passing it into QWebEnginePage()
does work. But it introduces a warning each time a webview is deallocated,
and I fear it may lead to crashes:

```
Release of profile requested but WebEnginePage still not deleted. Expect troubles !
```

I tried various combinations of parents for the profile and page, and
turning web._page into an unretained property, but could not figure it out.
Some Googling pulls up a bunch of other people who seem to have hit similar
issues with PyQt. If anyone has any ideas, they'd be welcome; in the mean
time, I guess we're better off using up some of the user's disk
space than sometimes failing to load.

The profile-in-advance code I tried is below:

```
diff --git a/qt/aqt/webview.py b/qt/aqt/webview.py
index 1c96112d8..4f3e91284 100644
--- a/qt/aqt/webview.py
+++ b/qt/aqt/webview.py
@@ -23,9 +23,49 @@ serverbaseurl = re.compile(r"^.+:\/\/[^\/]+")
 BridgeCommandHandler = Callable[[str], Any]

+def _create_profile(parent: QObject) -> QWebEngineProfile:
+    qwebchannel = ":/qtwebchannel/qwebchannel.js"
+    jsfile = QFile(qwebchannel)
+    if not jsfile.open(QIODevice.OpenModeFlag.ReadOnly):
+        print(f"Error opening '{qwebchannel}': {jsfile.error()}", file=sys.stderr)
+    jstext = bytes(cast(bytes, jsfile.readAll())).decode("utf-8")
+    jsfile.close()
+
+    script = QWebEngineScript()
+    script.setSourceCode(
+        jstext
+        + """
+            var pycmd, bridgeCommand;
+            new QWebChannel(qt.webChannelTransport, function(channel) {
+                bridgeCommand = pycmd = function (arg, cb) {
+                    var resultCB = function (res) {
+                        // pass result back to user-provided callback
+                        if (cb) {
+                            cb(JSON.parse(res));
+                        }
+                    }
+
+                    channel.objects.py.cmd(arg, resultCB);
+                    return false;
+                }
+                pycmd("domDone");
+            });
+        """
+    )
+    script.setWorldId(QWebEngineScript.ScriptWorldId.MainWorld)
+    script.setInjectionPoint(QWebEngineScript.InjectionPoint.DocumentReady)
+    script.setRunsOnSubFrames(False)
+
+    profile = QWebEngineProfile(parent)
+    profile.setHttpCacheType(QWebEngineProfile.HttpCacheType.NoCache)
+    profile.scripts().insert(script)
+    return profile
+
+
 class AnkiWebPage(QWebEnginePage):
-    def __init__(self, onBridgeCmd: BridgeCommandHandler) -> None:
-        QWebEnginePage.__init__(self)
+    def __init__(self, onBridgeCmd: BridgeCommandHandler, parent: QObject) -> None:
+        profile = _create_profile(parent)
+        QWebEnginePage.__init__(self, profile, parent)
         self._onBridgeCmd = onBridgeCmd
         self._setupBridge()
         self.open_links_externally = True
@@ -46,39 +86,6 @@ class AnkiWebPage(QWebEnginePage):
         self._channel.registerObject("py", self._bridge)
         self.setWebChannel(self._channel)

-        qwebchannel = ":/qtwebchannel/qwebchannel.js"
-        jsfile = QFile(qwebchannel)
-        if not jsfile.open(QIODevice.OpenModeFlag.ReadOnly):
-            print(f"Error opening '{qwebchannel}': {jsfile.error()}", file=sys.stderr)
-        jstext = bytes(cast(bytes, jsfile.readAll())).decode("utf-8")
-        jsfile.close()
-
-        script = QWebEngineScript()
-        script.setSourceCode(
-            jstext
-            + """
-            var pycmd, bridgeCommand;
-            new QWebChannel(qt.webChannelTransport, function(channel) {
-                bridgeCommand = pycmd = function (arg, cb) {
-                    var resultCB = function (res) {
-                        // pass result back to user-provided callback
-                        if (cb) {
-                            cb(JSON.parse(res));
-                        }
-                    }
-
-                    channel.objects.py.cmd(arg, resultCB);
-                    return false;
-                }
-                pycmd("domDone");
-            });
-        """
-        )
-        script.setWorldId(QWebEngineScript.ScriptWorldId.MainWorld)
-        script.setInjectionPoint(QWebEngineScript.InjectionPoint.DocumentReady)
-        script.setRunsOnSubFrames(False)
-        self.profile().scripts().insert(script)
-
     def javaScriptConsoleMessage(
         self,
         level: QWebEnginePage.JavaScriptConsoleMessageLevel,
@@ -228,7 +235,7 @@ class AnkiWebView(QWebEngineView):
     ) -> None:
         QWebEngineView.__init__(self, parent=parent)
         self.set_title(title)
-        self._page = AnkiWebPage(self._onBridgeCmd)
+        self._page = AnkiWebPage(self._onBridgeCmd, self)
         self._page.setBackgroundColor(
             self.get_window_bg_color(theme_manager.night_mode)
@@ -242,7 +249,6 @@ class AnkiWebView(QWebEngineView):
         self.requiresCol = True
         self.setPage(self._page)

-        self._page.profile().setHttpCacheType(QWebEngineProfile.HttpCacheType.NoCache)
         self.resetHandlers()
         self.allowDrops = False
         self._filterSet = False
```
2021-12-08 21:20:39 +10:00
.buildkite move node_modules into root folder [action required] 2021-10-07 11:42:27 +10:00
.github/ISSUE_TEMPLATE update bug report template 2021-11-12 12:20:02 +10:00
cargo update Rust deps 2021-12-03 20:35:53 +10:00
docs update Visual Studio instructions 2021-11-29 12:32:39 +10:00
ftl Add browser action to create note copy (#1535) 2021-12-08 08:40:48 +10:00
platforms use x86 binaries for clang format/protobuf on Mac 2021-10-16 18:07:39 +10:00
proto Change Notetype UI Rework (#1499) 2021-11-24 12:09:55 +10:00
pylib add unbury_cards() op 2021-12-08 09:44:47 +10:00
python unpin regex & update Python deps 2021-12-08 15:11:47 +10:00
qt fix webviews sometimes failing to load, by enabling cache 2021-12-08 21:20:39 +10:00
rslib fix error when gathering new cards in reverse position 2021-12-06 17:08:00 +10:00
sass Flip arrows of Bootstrap-styled <select>s for RTL langs (#1526) 2021-12-06 18:40:26 +10:00
scripts fix cargo-env on darwin-aarch64 2021-12-04 17:03:03 +10:00
ts Decrease button and tag size for Linux and Windows (#1532) 2021-12-06 19:01:15 +10:00
.bazelignore move node_modules into root folder [action required] 2021-10-07 11:42:27 +10:00
.bazelrc update to Rust 1.57 + latest rules_rust 2021-12-03 20:35:52 +10:00
.bazelversion update Bazel version 2021-10-01 20:54:48 +10:00
.gitattributes try again to improve GitHub's language stats 2021-01-20 13:20:45 +10:00
.gitignore move node_modules into root folder [action required] 2021-10-07 11:42:27 +10:00
.prettierignore Mathjax editor improvements (#1502) 2021-11-23 10:27:32 +10:00
.prettierrc Mathjax editor improvements (#1502) 2021-11-23 10:27:32 +10:00
bazel.bat initial Bazel conversion 2020-11-01 14:26:58 +10:00
BUILD.bazel Fix prettier after moving node_modules to repo dir (#1413) 2021-10-09 10:13:14 +10:00
Cargo.lock update Rust deps 2021-12-03 20:35:53 +10:00
Cargo.toml Garbage collect unused Fluent strings (#1482) 2021-11-12 18:19:01 +10:00
CONTRIBUTORS Update CONTRIBUTORS (#1527) 2021-12-05 12:33:40 +10:00
defs.bzl update to edition 2021 2021-11-18 20:51:10 +10:00
late_deps.bzl download wheels using rules_python 2021-10-15 16:02:26 +10:00
LICENSE move aqt_data into source folder; implement wheel building 2020-11-04 12:14:03 +10:00
package.json update Node deps 2021-12-03 20:35:53 +10:00
pkgkey.asc add public key for release signing 2018-09-18 10:40:45 +10:00
README.md fix build badge 2021-06-24 09:21:56 +10:00
repos.bzl update translations 2021-12-08 10:23:27 +10:00
run get PyQt working directly with ./run on macOS 2021-10-16 18:07:29 +10:00
run.bat enable Python warnings when running 2020-12-16 14:59:04 +10:00
SECURITY.md add SECURITY.md 2021-09-22 22:55:19 +10:00
WORKSPACE move node_modules into root folder [action required] 2021-10-07 11:42:27 +10:00
yarn.lock update Node deps 2021-12-03 20:35:53 +10:00

Anki

Build status

This repo contains the source code for the computer version of Anki.

If you'd like to try development builds of Anki but don't feel comfortable building the code, please see https://betas.ankiweb.net/

For more information on building, please see Development.