gnucash future: Multiple changes pushed

John Ralls jralls at code.gnucash.org
Sat Aug 22 14:34:28 EDT 2026


Updated	 via  https://github.com/Gnucash/gnucash/commit/625887b8 (commit)
	 via  https://github.com/Gnucash/gnucash/commit/c0db031b (commit)
	 via  https://github.com/Gnucash/gnucash/commit/f9f9e630 (commit)
	 via  https://github.com/Gnucash/gnucash/commit/c3a7be18 (commit)
	 via  https://github.com/Gnucash/gnucash/commit/d1eb9b4b (commit)
	 via  https://github.com/Gnucash/gnucash/commit/a6c2d024 (commit)
	from  https://github.com/Gnucash/gnucash/commit/b76358c0 (commit)



commit 625887b8fa2f5b5278caa5406ae5b2e4907a60e9
Merge: b76358c050 c0db031bb1
Author: John Ralls <jralls at ceridwen.us>
Date:   Sat Aug 22 11:29:41 2026 -0700

    Merge John Ralls's 'win32-webview2' into future


commit c0db031bb11da76f47575af9ac2a5292d2ccda76
Author: John Ralls <jralls at ceridwen.us>
Date:   Sat Aug 15 12:52:44 2026 -0700

    [html] Implement Open Link in a new Window menu item on WebView2.
    
    This web page context menu item, always ignored on the WebKit
    implementation, opened a new native WebView2 window with an
    error message because it couldn't interpret the Gnucash-special
    URL. Implement GnuCash special URL handling for register and price
    type URLs and remove the menu item for multicolumn report
    subreports because they must open in the same tab as the master
    report.
    
    Note that this does not implement handling the context menu item
    in WebKit2.
    
    Assisted-by: Claude Code Sonnet 5
    Signed-off: John Ralls <jralls at ceridwen.us>

diff --git a/gnucash/gnome/gnc-plugin-page-report.cpp b/gnucash/gnome/gnc-plugin-page-report.cpp
index 1541de4ed0..ec22e78624 100644
--- a/gnucash/gnome/gnc-plugin-page-report.cpp
+++ b/gnucash/gnome/gnc-plugin-page-report.cpp
@@ -2096,13 +2096,26 @@ void
 gnc_main_window_open_report_url(const char * url, GncMainWindow *window)
 {
     GncPluginPage *reportPage;
+    gint report_id;
 
     DEBUG( "report url: [%s]\n", url );
 
     if (window)
         g_return_if_fail(GNC_IS_MAIN_WINDOW(window));
 
-    reportPage = gnc_plugin_page_report_new( 42 /* url? */ );
+    /* Duplicating report pages copies an uncopiable reference leading
+     * to crashes so don't allow multicolumn subreports to open in new
+     * GncMainWindows.
+     */
+    if (strncmp (url, "id=", 3) != 0 ||
+        (report_id = gnc_report_id_string_to_report_id (url + 3)) < 0)
+    {
+        PWARN ("gnc_main_window_open_report_url: badly formed report url '%s'", url);
+        return;
+    }
+
+    reportPage = gnc_plugin_page_report_new( report_id );
+    gnc_plugin_page_set_use_new_window (reportPage, TRUE);
     gnc_main_window_open_page( window, reportPage );
 }
 
diff --git a/gnucash/gnome/gnc-plugin-page-report.h b/gnucash/gnome/gnc-plugin-page-report.h
index 9e1b914a56..5f9201b3fe 100644
--- a/gnucash/gnome/gnc-plugin-page-report.h
+++ b/gnucash/gnome/gnc-plugin-page-report.h
@@ -84,8 +84,12 @@ void gnc_plugin_page_report_reload (GncPluginPageReport *report);
 
 // entry-point from scm menu-extension callback [gnc:menu-extension].
 void       gnc_main_window_open_report (int report_id, GncMainWindow *window);
-// directly called through from above
-void       gnc_main_window_open_report_url (const char * url, GncMainWindow *window);
+/**
+ * Open a report url in a new GncMainWindow.
+ * @param url: The url for the object to open.
+ * @param window: The GncMainWindow holding the calling report
+ */
+void gnc_main_window_open_report_url (const char * url, GncMainWindow *window);
 
 #ifdef __cplusplus
 }
diff --git a/gnucash/gnome/gnc-plugin-report-system.c b/gnucash/gnome/gnc-plugin-report-system.c
index 5cbf4e16f0..1ad2613546 100644
--- a/gnucash/gnome/gnc-plugin-report-system.c
+++ b/gnucash/gnome/gnc-plugin-report-system.c
@@ -204,28 +204,17 @@ gnc_report_system_options_url_cb (const char *location, const char *label,
     }
 }
 
+/* Loads a subreport from a multicolumn report. Subreports must be
+ * loaded in the same tab as the multicolumn report, they can't stand
+ * on their own.
+ */
 static gboolean
 gnc_report_system_report_url_cb (const char *location, const char *label,
                         gboolean new_window, GNCURLResult *result)
 {
     g_return_val_if_fail (location != NULL, FALSE);
     g_return_val_if_fail (result != NULL, FALSE);
-
-    /* make a new window if necessary */
-    if (new_window)
-    {
-        char *url;
-
-        url = gnc_build_url (URL_TYPE_REPORT, location, label);
-        gnc_main_window_open_report_url (url, GNC_MAIN_WINDOW(result->parent));
-        g_free (url);
-
-        result->load_to_stream = FALSE;
-    }
-    else
-    {
-        result->load_to_stream = TRUE;
-    }
+    result->load_to_stream = TRUE;
 
     return TRUE;
 }
diff --git a/gnucash/gnome/top-level.c b/gnucash/gnome/top-level.c
index 19c8b32f72..3eb0c84cba 100644
--- a/gnucash/gnome/top-level.c
+++ b/gnucash/gnome/top-level.c
@@ -209,6 +209,8 @@ gnc_html_register_url_cb (const char *location, const char *label,
     }
 
     page = gnc_plugin_page_register_new (account, FALSE);
+    if (new_window)
+        gnc_plugin_page_set_use_new_window (page, TRUE);
     gnc_main_window_open_page (GNC_MAIN_WINDOW (result->parent), page);
     if (split)
     {
diff --git a/gnucash/html/gnc-html-webview2-p.hpp b/gnucash/html/gnc-html-webview2-p.hpp
index 947f6fa6d2..4920ac7bc7 100644
--- a/gnucash/html/gnc-html-webview2-p.hpp
+++ b/gnucash/html/gnc-html-webview2-p.hpp
@@ -52,6 +52,15 @@ struct GncHtmlWebview2Private
     ICoreWebView2* webview;
     EventRegistrationToken nav_starting_token;
     gboolean has_nav_starting_token;
+    EventRegistrationToken new_window_token;
+    gboolean has_new_window_token;
+
+    /* Only available on newer WebView2 runtimes (ICoreWebView2_11);
+     * nullptr if the QueryInterface for it failed, in which case the
+     * context menu is simply never customized. */
+    ICoreWebView2_11* webview11;
+    EventRegistrationToken context_menu_token;
+    gboolean has_context_menu_token;
 
     gboolean environment_creating;
 
diff --git a/gnucash/html/gnc-html-webview2.cpp b/gnucash/html/gnc-html-webview2.cpp
index d678fc466d..0b6604fc70 100644
--- a/gnucash/html/gnc-html-webview2.cpp
+++ b/gnucash/html/gnc-html-webview2.cpp
@@ -191,6 +191,26 @@ public:
                                       ICoreWebView2NavigationStartingEventArgs* args) override;
 };
 
+class GncWebView2NewWindowHandler final
+    : public GncWebView2Sink<ICoreWebView2NewWindowRequestedEventHandler>
+{
+public:
+    using GncWebView2Sink::GncWebView2Sink;
+
+    HRESULT STDMETHODCALLTYPE Invoke (ICoreWebView2* sender,
+                                      ICoreWebView2NewWindowRequestedEventArgs* args) override;
+};
+
+class GncWebView2ContextMenuHandler final
+    : public GncWebView2Sink<ICoreWebView2ContextMenuRequestedEventHandler>
+{
+public:
+    using GncWebView2Sink::GncWebView2Sink;
+
+    HRESULT STDMETHODCALLTYPE Invoke (ICoreWebView2* sender,
+                                      ICoreWebView2ContextMenuRequestedEventArgs* args) override;
+};
+
 /* Minimal hand-rolled implementation of
  * ICoreWebView2EnvironmentOptions (verified against WebView2.h
  * provided by MSYS2 mingw-w64-webview2-loader package version
@@ -473,6 +493,25 @@ controller_created (GncHtmlWebview2* self, HRESULT result,
         priv->webview->add_NavigationStarting (nav_handler, &priv->nav_starting_token);
         priv->has_nav_starting_token = TRUE;
         nav_handler->Release ();
+
+        auto new_window_handler = new GncWebView2NewWindowHandler (self);
+        priv->webview->add_NewWindowRequested (new_window_handler, &priv->new_window_token);
+        priv->has_new_window_token = TRUE;
+        new_window_handler->Release ();
+
+        /* Only available on newer WebView2 runtimes; if the QI fails,
+         * the context menu is simply never customized (see
+         * context_menu_requested() below). */
+        HRESULT hr11 = priv->webview->QueryInterface (
+            IID_ICoreWebView2_11, reinterpret_cast<void**> (&priv->webview11));
+        if (SUCCEEDED (hr11) && priv->webview11)
+        {
+            auto menu_handler = new GncWebView2ContextMenuHandler (self);
+            HRESULT hr_add = priv->webview11->add_ContextMenuRequested (
+                menu_handler, &priv->context_menu_token);
+            priv->has_context_menu_token = SUCCEEDED (hr_add);
+            menu_handler->Release ();
+        }
     }
 
     flush_pending (self);
@@ -535,6 +574,151 @@ GncWebView2NavStartingHandler::Invoke (ICoreWebView2* /*sender*/,
     return S_OK;
 }
 
+/* Fires when the user picks "Open link in new window" from the context
+ * menu (or a page navigates with a new-window target). Left unhandled,
+ * WebView2's default action is to pop up a bare native WebView2 window
+ * that can't do anything useful with our gnc-register:/gnc-report:/etc.
+ * links. Always claim the event and decode the URI the same way a
+ * plain click does, but with new_window forced on so link types that
+ * can safely honor it (e.g. account/register links) open a genuine
+ * separate GnuCash window as the user asked. Report-type links can't
+ * safely honor it -- see show_url()'s handling of URL_TYPE_REPORT --
+ * so they just navigate in place, same as a plain click; the context
+ * menu item is greyed out for those anyway, see
+ * context_menu_requested() below. -- Claude Code, 2026 */
+static void
+new_window_requested (GncHtmlWebview2* self,
+                                    ICoreWebView2NewWindowRequestedEventArgs* args)
+{
+    args->put_Handled (TRUE);
+
+    LPWSTR wuri = nullptr;
+    if (FAILED (args->get_Uri (&wuri)) || !wuri)
+        return;
+
+    gchar* uri = g_utf16_to_utf8 (reinterpret_cast<const gunichar2*> (wuri), -1,
+                                  nullptr, nullptr, nullptr);
+    CoTaskMemFree (wuri);
+    if (!uri)
+        return;
+
+    gchar* location = nullptr;
+    gchar* label = nullptr;
+    URLType scheme = gnc_html_parse_url (GNC_HTML (self), uri, &location, &label);
+
+    show_url (GNC_HTML (self), scheme, location, label, TRUE);
+
+    g_free (location);
+    g_free (label);
+    g_free (uri);
+}
+
+HRESULT STDMETHODCALLTYPE
+GncWebView2NewWindowHandler::Invoke (ICoreWebView2* /*sender*/,
+                                     ICoreWebView2NewWindowRequestedEventArgs* args)
+{
+    new_window_requested (m_html, args);
+    return S_OK;
+}
+
+/* "Open link in new window" can't be honored for report-type links
+ * (see show_url()'s handling of URL_TYPE_REPORT and
+ * new_window_requested() above), so remove that item from the context
+ * menu when it's raised on one, rather than offering an action that
+ * silently does nothing when picked. -- Claude Code, 2026 */
+static void
+context_menu_requested (GncHtmlWebview2* self,
+                                      ICoreWebView2ContextMenuRequestedEventArgs* args)
+{
+    ICoreWebView2ContextMenuTarget* target = nullptr;
+    if (FAILED (args->get_ContextMenuTarget (&target)) || !target)
+        return;
+
+    BOOL has_link = FALSE;
+    LPWSTR wuri = nullptr;
+    HRESULT hr = target->get_HasLinkUri (&has_link);
+    if (FAILED (hr) || !has_link || FAILED (target->get_LinkUri (&wuri)) || !wuri)
+    {
+        target->Release ();
+        return;
+    }
+    target->Release ();
+
+    gchar* uri = g_utf16_to_utf8 (reinterpret_cast<const gunichar2*> (wuri), -1,
+                                  nullptr, nullptr, nullptr);
+    CoTaskMemFree (wuri);
+    if (!uri)
+        return;
+
+    gchar* location = nullptr;
+    gchar* label = nullptr;
+    URLType scheme = gnc_html_parse_url (GNC_HTML (self), uri, &location, &label);
+    g_free (uri);
+    g_free (location);
+    g_free (label);
+
+    if (g_strcmp0 (scheme, URL_TYPE_REPORT) != 0)
+        return;
+
+    ICoreWebView2ContextMenuItemCollection* items = nullptr;
+    if (FAILED (args->get_MenuItems (&items)) || !items)
+        return;
+
+    UINT32 count = 0;
+    items->get_Count (&count);
+    /* Loop index only advances when the current item is kept --
+     * RemoveValueAtIndex() shifts everything after it down by one, so
+     * re-examining the same index picks up what used to be next. */
+    for (UINT32 i = 0; i < count; )
+    {
+        ICoreWebView2ContextMenuItem* item = nullptr;
+        if (FAILED (items->GetValueAtIndex (i, &item)) || !item)
+        {
+            ++i;
+            continue;
+        }
+
+        bool removed = false;
+        LPWSTR wname = nullptr;
+        if (SUCCEEDED (item->get_Name (&wname)) && wname)
+        {
+            gchar* name = g_utf16_to_utf8 (reinterpret_cast<const gunichar2*> (wname), -1,
+                                           nullptr, nullptr, nullptr);
+            CoTaskMemFree (wname);
+            /* "openLinkInNewWindow" is the documented stable Name for
+             * this default item; also remove "openLinkInNewTab" in
+             * case a future WebView2 runtime offers it here too.
+             * Disabling via put_IsEnabled(FALSE) is the documented way
+             * to grey out an item, and the SDK reports it succeeding
+             * (verified: get_IsEnabled() reads back FALSE afterward),
+             * but this WebView2 build doesn't actually reflect that in
+             * the rendered menu for this particular built-in command --
+             * removing the item outright is more fundamental and does
+             * take effect (verified). */
+            if (!g_strcmp0 (name, "openLinkInNewWindow") ||
+                !g_strcmp0 (name, "openLinkInNewTab"))
+            {
+                items->RemoveValueAtIndex (i);
+                --count;
+                removed = true;
+            }
+            g_free (name);
+        }
+        item->Release ();
+        if (!removed)
+            ++i;
+    }
+    items->Release ();
+}
+
+HRESULT STDMETHODCALLTYPE
+GncWebView2ContextMenuHandler::Invoke (ICoreWebView2* /*sender*/,
+                                       ICoreWebView2ContextMenuRequestedEventArgs* args)
+{
+    context_menu_requested (m_html, args);
+    return S_OK;
+}
+
 // *****************************************************************************
 
 static void
@@ -667,6 +851,11 @@ gnc_html_webview2_init (GncHtmlWebview2* self)
      priv->webview = nullptr;
      priv->nav_starting_token = EventRegistrationToken {};
      priv->has_nav_starting_token = FALSE;
+     priv->new_window_token = EventRegistrationToken {};
+     priv->has_new_window_token = FALSE;
+     priv->webview11 = nullptr;
+     priv->context_menu_token = EventRegistrationToken {};
+     priv->has_context_menu_token = FALSE;
      priv->environment_creating = FALSE;
      priv->navigating_internally = FALSE;
      priv->disposed = FALSE;
@@ -737,6 +926,24 @@ gnc_html_webview2_dispose (GObject* obj)
           priv->has_nav_starting_token = FALSE;
      }
 
+     if (priv->webview != nullptr && priv->has_new_window_token)
+     {
+          priv->webview->remove_NewWindowRequested (priv->new_window_token);
+          priv->has_new_window_token = FALSE;
+     }
+
+     if (priv->webview11 != nullptr && priv->has_context_menu_token)
+     {
+          priv->webview11->remove_ContextMenuRequested (priv->context_menu_token);
+          priv->has_context_menu_token = FALSE;
+     }
+
+     if (priv->webview11 != nullptr)
+     {
+          priv->webview11->Release();
+          priv->webview11 = nullptr;
+     }
+
      if (priv->controller != nullptr)
      {
           priv->controller->Close();

commit f9f9e63045fa6ebdfb5d576470a3473a1133a74c
Author: John Ralls <jralls at ceridwen.us>
Date:   Sat Aug 15 12:24:11 2026 -0700

    [html] Change the meaning of new_window in show_url.
    
    New meaning is open the url in a new GncMainWindow. The old meaning
    was the currently open report tab can't handle this link type so it
    needs to open in a separate tab. The hint to override the type test
    was never passed and the effect was a single function call immediately
    after the test; there was no need for a variable, local or otherwise.

diff --git a/gnucash/html/gnc-html-webkit2.cpp b/gnucash/html/gnc-html-webkit2.cpp
index c925a89914..63e43b4e73 100644
--- a/gnucash/html/gnc-html-webkit2.cpp
+++ b/gnucash/html/gnc-html-webkit2.cpp
@@ -107,7 +107,7 @@ static void webkit_resource_load_started_cb (WebKitWebView *web_view,
 static gchar* handle_embedded_object( GncHtmlWebkit* self, gchar* html_str );
 static void impl_webkit_show_url( GncHtml* self, URLType type,
                                   const gchar* location, const gchar* label,
-                                  gboolean new_window_hint );
+                                  gboolean new_window );
 static void impl_webkit_show_data( GncHtml* self, const gchar* data, int datalen );
 static void impl_webkit_reload( GncHtml* self, gboolean force_rebuild );
 static void impl_webkit_copy_to_clipboard( GncHtml* self );
@@ -760,10 +760,9 @@ impl_webkit_show_data( GncHtml* self, const gchar* data, int datalen )
 static void
 impl_webkit_show_url( GncHtml* self, URLType type,
                       const gchar* location, const gchar* label,
-                      gboolean new_window_hint )
+                      gboolean new_window )
 {
      GncHTMLUrlCB url_handler = nullptr;
-     bool new_window = false;
      bool stream_loaded = false;
 
      g_return_if_fail( self != nullptr );
@@ -772,23 +771,8 @@ impl_webkit_show_url( GncHtml* self, URLType type,
 
      auto priv = GNC_HTML_WEBKIT_GET_PRIVATE(self);
 
-     /* make sure it's OK to show this URL type in this window */
-     if ( new_window_hint == 0 )
-     {
-          if ( priv->base.urltype_cb )
-          {
-               new_window = !((priv->base.urltype_cb)( type ));
-          }
-     }
-     else
-     {
-          new_window = true;
-     }
-
-     if ( !new_window )
-     {
+     if ( priv->base.urltype_cb && priv->base.urltype_cb( type ) )
           gnc_html_cancel( GNC_HTML(self) );
-     }
 
      if ( gnc_html_url_handlers )
      {
diff --git a/gnucash/html/gnc-html-webview2.cpp b/gnucash/html/gnc-html-webview2.cpp
index 638ae79464..d678fc466d 100644
--- a/gnucash/html/gnc-html-webview2.cpp
+++ b/gnucash/html/gnc-html-webview2.cpp
@@ -104,7 +104,7 @@ static char error_404_body[] = N_("The specified URL could not be loaded.");
 
 static void show_url (GncHtml* self, URLType type,
                                     const gchar* location, const gchar* label,
-                                    gboolean new_window_hint);
+                                    gboolean new_window);
 static void show_data (GncHtml* self, const gchar* data, int datalen);
 static void reload (GncHtml* self, gboolean force_rebuild);
 static void copy_to_clipboard (GncHtml* self);
@@ -1116,10 +1116,9 @@ show_data (GncHtml* self, const gchar* data, int datalen)
 static void
 show_url (GncHtml* self, URLType type,
                         const gchar* location, const gchar* label,
-                        gboolean new_window_hint)
+                        gboolean new_window)
 {
      GncHTMLUrlCB url_handler = nullptr;
-     bool new_window = false;
      bool stream_loaded = false;
 
      g_return_if_fail (self != nullptr);
@@ -1128,23 +1127,8 @@ show_url (GncHtml* self, URLType type,
 
      auto priv = GNC_HTML_WEBVIEW2_GET_PRIVATE(self);
 
-     /* make sure it's OK to show this URL type in this window */
-     if (new_window_hint == 0)
-     {
-          if (priv->base.urltype_cb)
-          {
-               new_window = !((priv->base.urltype_cb)(type));
-          }
-     }
-     else
-     {
-          new_window = true;
-     }
-
-     if (!new_window)
-     {
-          gnc_html_cancel (GNC_HTML(self));
-     }
+     if (priv->base.urltype_cb && priv->base.urltype_cb (type))
+          gnc_html_cancel (GNC_HTML (self));
 
      if (gnc_html_url_handlers)
      {
diff --git a/gnucash/html/gnc-html.cpp b/gnucash/html/gnc-html.cpp
index e2f2acb494..46c1beb719 100644
--- a/gnucash/html/gnc-html.cpp
+++ b/gnucash/html/gnc-html.cpp
@@ -360,7 +360,7 @@ gnc_html_show_data( GncHtml* self, const gchar* data, int datalen ) noexcept
 void
 gnc_html_show_url( GncHtml* self, URLType type,
                    const gchar* location, const gchar* label,
-                   gboolean new_window_hint ) noexcept
+                   gboolean new_window ) noexcept
 {
     g_return_if_fail( self != nullptr );
     g_return_if_fail( GNC_IS_HTML(self) );
@@ -369,7 +369,7 @@ gnc_html_show_url( GncHtml* self, URLType type,
 
     if ( GNC_HTML_GET_CLASS(self)->show_url != nullptr )
     {
-        GNC_HTML_GET_CLASS(self)->show_url( self, lc_type, location, label, new_window_hint );
+        GNC_HTML_GET_CLASS(self)->show_url( self, lc_type, location, label, new_window );
     }
     else
     {
diff --git a/gnucash/html/gnc-html.h b/gnucash/html/gnc-html.h
index 6e4b9d744a..d8691548ab 100644
--- a/gnucash/html/gnc-html.h
+++ b/gnucash/html/gnc-html.h
@@ -87,6 +87,14 @@ typedef struct
 typedef gboolean (* GncHTMLObjectCB)(GncHtml* html, gpointer eb,
                                      gpointer data);
 typedef gboolean (* GncHTMLStreamCB)(const gchar* location, gchar** data, int* datalen);
+/** Function pointer type for loading a URL-like object into a web page.
+ *
+ * @param location: URL or other indicator to the resource. On multi-column reports this may be the index of the suberport.
+ * @param label: The label to use for the notebook tab containing the item.
+ * @param new_window: Open the tab in a new GncMainWindow if true
+ * @param result: A GNCURLResult*
+ * @returns true if the handler succeeded.
+ */
 typedef gboolean (* GncHTMLUrlCB)(const gchar* location, const gchar* label,
                                   gboolean new_window, GNCURLResult* result);
 
@@ -128,7 +136,7 @@ struct _GncHtmlClass
                       URLType type,
                       const gchar* location,
                       const gchar* label,
-                      gboolean new_window_hint );
+                      gboolean new_window );
     void (*show_data)( GncHtml* html, const gchar* data, int datalen );
     void (*reload)( GncHtml* html, gboolean force_rebuild );
     void (*copy_to_clipboard)( GncHtml* html );
@@ -175,7 +183,7 @@ void gnc_html_destroy( GncHtml* html ) NOEXCEPT;
  * @param html GncHtml object
  */
 void gnc_html_show_url( GncHtml* html, URLType type, const gchar* location,
-                        const gchar* label, gboolean new_window_hint ) NOEXCEPT;
+                        const gchar* label, gboolean new_window ) NOEXCEPT;
 
 /**
  * Displays an HTML string in a GncHtml object.

commit c3a7be182040fb0b380ffeb66f88987731a33c2d
Author: John Ralls <jralls at ceridwen.us>
Date:   Sun Jan 18 09:47:50 2026 -0800

    Remove the MINGW/MINGW64 distinction
    
    The original Mingw project appears to have passed away with the demise last
    year of OSDN if not before. Even the original mingw.org domain now points to
    a Q&A website.
    
    Note for backend/dbi/CMakeLists.txt that WINSOCK_LIB needs to be defined on
    Windows no matter what compiler is used.
    
    The section fiddling with CMAKE_FRAMEWORK_PATH was a relic of the pre-jhbuild
    Windows build. It's quite wrong now.

diff --git a/CMakeLists.txt b/CMakeLists.txt
index fe34cd87f4..81235a9d03 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -126,23 +126,12 @@ set(SYSCONFDIR_BUILD ${CMAKE_BINARY_DIR}/etc)
 set(LIBEXECDIR_BUILD ${CMAKE_BINARY_DIR}/libexec)
 set(BINDIR_BUILD     ${CMAKE_BINARY_DIR}/bin)
 
-# We need to distinguish between MinGW.org and MinGW-w64:
-if (MINGW)
-  string(FIND ${CMAKE_C_COMPILER} "msys2" IS_MSYS2)
-  if(${IS_MSYS2} GREATER_EQUAL 0)
-    set(MINGW64 ON)
-  endif()
-endif()
-
 # Find a proper bash executable
 
 set(GNC_SHELL $ENV{GNC_SHELL})
 if (GNC_SHELL) # Replacing this with if ($ENV{GNC_SHELL}) doesn't work.
   # Allow shell override by setting the GNC_SHELL environment variable
   set(SHELL ${GNC_SHELL})
-elseif (MINGW AND NOT MINGW64)
-  # Old mingw's bash is on on the path, so hard-code it for now
-  set(SHELL ${CMAKE_PREFIX_PATH}/mingw/msys/1.0/bin/bash.exe)
 else()
   find_package(UnixCommands)
   if (BASH)
@@ -187,23 +176,6 @@ set(VCS_INFO_FILE ${VCS_INFO_BASE_DIR}/libgnucash/core-utils/gnc-vcs-info.h CACH
 
 
 if (WIN32)
-  # Help Windows find the various dependencies. We assume here that the standard advice for building
-  # GnuCash has been followed and that these dependencies live underneath something like C:/GCDEV, which
-  # should be provided as CMAKE_PREFIX_PATH on the CMake command line:
-  #      cmake -D CMAKE_PREFIX_PATH=c/gcdev -G "MSYS Makefiles" path/to/gnucash/sources
-  #
-  set(CMAKE_C_IMPLICIT_LINK_LIBRARIES "")
-  set(CMAKE_FRAMEWORK_PATH_TMP ${CMAKE_PREFIX_PATH})
-  set(DEV_SUBDIRS aqbanking gnome guile gwenhywfar libgsf libofx libsoup libxslt webkit)
-  foreach(subdir ${DEV_SUBDIRS})
-    list(APPEND CMAKE_FRAMEWORK_PATH_TMP ${CMAKE_PREFIX_PATH}/${subdir})
-  endforeach()
-  # Setting CMAKE_FRAMEWORK_PATH, which is intended for OS X, is a terrible hack here. But I don't
-  # want to mess with CMAKE_PREFIX_PATH.
-  set(CMAKE_FRAMEWORK_PATH "${CMAKE_FRAMEWORK_PATH_TMP}")
-  #set(PKG_CONFIG_EXECUTABLE ${CMAKE_PREFIX_PATH}/gnome/bin/pkg-config.exe)
-  set(SWIG_EXECUTABLE swig)
-  #set(SWIG_EXECUTABLE ${CMAKE_PREFIX_PATH}/swig/swig.exe)
   find_path(REGEX_INCLUDE_PATH regex.h)
   find_path(REGEX_LIB_PATH libregex.dll.a)
   set(REGEX_LDFLAGS "-L${REGEX_LIB_PATH} -lregex")
diff --git a/common/cmake_modules/GncAddSchemeTargets.cmake b/common/cmake_modules/GncAddSchemeTargets.cmake
index 401ceb2d8b..e50a9907c4 100644
--- a/common/cmake_modules/GncAddSchemeTargets.cmake
+++ b/common/cmake_modules/GncAddSchemeTargets.cmake
@@ -177,7 +177,7 @@ function(gnc_add_scheme_targets _TARGET)
   set(build_bindir ${BINDIR_BUILD})
   set(build_libdir ${LIBDIR_BUILD})
   set(build_datadir ${DATADIR_BUILD})
-  if(MINGW64 AND ${GUILE_EFFECTIVE_VERSION} VERSION_LESS 2.2)
+  if(MINGW AND ${GUILE_EFFECTIVE_VERSION} VERSION_LESS 2.2)
     make_unix_path(build_bindir)
     make_unix_path(build_libdir)
     make_unix_path(build_datadir)
@@ -216,7 +216,7 @@ function(gnc_add_scheme_targets _TARGET)
   set(_GUILE_LOAD_PATH "${current_srcdir}" "${current_bindir}" "${current_bindir}/deprecated")
   set(_GUILE_LOAD_COMPILED_PATH "${current_bindir}")
   # VERSION_GREATER_EQUAL introduced in CMake 3.7.
-  if(MINGW64 AND (${GUILE_EFFECTIVE_VERSION} VERSION_GREATER_EQUAL 2.2))
+  if(MINGW AND (${GUILE_EFFECTIVE_VERSION} VERSION_GREATER_EQUAL 2.2))
     if (NOT (DEFINED ENV{GUILE_LOAD_PATH} AND DEFINED ENV{GUILE_LOAD_COMPILED_PATH}))
       message(FATAL_ERROR "$GUILE_LOAD_PATH and $GUILE_LOAD_COMPILED_PATH must be defined in the environment to configure GnuCash on Microsoft Windows.")
     endif()
@@ -251,7 +251,7 @@ function(gnc_add_scheme_targets _TARGET)
       if (__DEBUG)
         message("add_custom_command: output = ${output_file}")
       endif()
-      if (MINGW64)
+      if (MINGW)
         set(fpath "")
         file(TO_CMAKE_PATH "$ENV{PATH}" fpath)
         set(LIBRARY_PATH "PATH=${BINDIR_BUILD};${fpath}")
@@ -263,15 +263,15 @@ function(gnc_add_scheme_targets _TARGET)
         set (LIBRARY_PATH "DYLD_LIBRARY_PATH=${LIBDIR_BUILD}:${LIBDIR_BUILD}/gnucash:$ENV{DYLD_LIBRARY_PATH}")
       endif()
       set(_GNC_MODULE_PATH "")
-      if(MINGW64)
+      if(MINGW)
         set(_GNC_MODULE_PATH "${build_bindir}")
       else()
         set(_GNC_MODULE_PATH "${LIBDIR_BUILD}" "${LIBDIR_BUILD}/gnucash" "${GNC_MODULE_PATH}")
       endif()
-      if(NOT MINGW64 OR ${GUILE_EFFECTIVE_VERSION} VERSION_LESS 2.2)
+      if(NOT MINGW OR ${GUILE_EFFECTIVE_VERSION} VERSION_LESS 2.2)
         make_unix_path_list(_GUILE_LOAD_PATH)
         make_unix_path_list(_GUILE_LOAD_COMPILED_PATH)
-      elseif(MINGW64)
+      elseif(MINGW)
         make_win32_path_list(_GUILE_LOAD_PATH)
         make_win32_path_list(_GUILE_LOAD_COMPILED_PATH)
       endif()
diff --git a/common/cmake_modules/GncAddTest.cmake b/common/cmake_modules/GncAddTest.cmake
index 707e46deeb..9ab75771b3 100644
--- a/common/cmake_modules/GncAddTest.cmake
+++ b/common/cmake_modules/GncAddTest.cmake
@@ -8,7 +8,7 @@ function(get_guile_env)
   set(_relative_site_dir "${CMAKE_BINARY_DIR}/${GUILE_REL_SITEDIR}")
   set(_relative_cache_dir "${CMAKE_BINARY_DIR}/${GUILE_REL_SITECCACHEDIR}")
 
-  if (MINGW64)
+  if (MINGW)
     set(fpath "")
     set(path $ENV{PATH})
     list(INSERT path 0 ${CMAKE_BINARY_DIR}/bin)
@@ -50,7 +50,7 @@ function(get_guile_env)
   )
   set(_guile_load_compiled_path "${guile_load_compiled_paths}")
 
-  if (MINGW64 AND ${GUILE_EFFECTIVE_VERSION} VERSION_LESS 2.2)
+  if (MINGW AND ${GUILE_EFFECTIVE_VERSION} VERSION_LESS 2.2)
     set(new_path "")
     foreach(load_item ${_guile_load_path})
       make_unix_path(load_item)
@@ -66,7 +66,7 @@ function(get_guile_env)
     set(_guile_load_compiled_path ${new_path})
   endif()
 
-  if (NOT MINGW64 OR ${GUILE_EFFECTIVE_VERSION} VERSION_LESS 2.2)
+  if (NOT MINGW OR ${GUILE_EFFECTIVE_VERSION} VERSION_LESS 2.2)
     make_unix_path_list(_guile_load_path)
     make_unix_path_list(_guile_load_compiled_path)
   else()
@@ -81,7 +81,7 @@ function(get_guile_env)
     "GUILE_LOAD_COMPILED_PATH=${_guile_load_compiled_path}"
     "GUILE_WARN_DEPRECATED=detailed"
   )
-  if (MINGW64)
+  if (MINGW)
     list(APPEND _guile_env "PATH=${fpath}")
   elseif (APPLE)
     list(APPEND _guile_env "DYLD_LIBRARY_PATH=${_gnc_module_path}:$ENV{DYLD_LIBRARY_PATH}")
diff --git a/libgnucash/backend/dbi/test/CMakeLists.txt b/libgnucash/backend/dbi/test/CMakeLists.txt
index 20ff79b34b..19b9016a38 100644
--- a/libgnucash/backend/dbi/test/CMakeLists.txt
+++ b/libgnucash/backend/dbi/test/CMakeLists.txt
@@ -48,7 +48,7 @@ if (WITH_SQL)
     file(MAKE_DIRECTORY ${temp_dir})
   endif()
 
-  if (MINGW64)
+  if (MINGW)
     string(REGEX REPLACE "\\\\" "/" temp_dir ${temp_dir})
   endif()
 

commit d1eb9b4bd7c017b9b1ad99d5864543b5c458c902
Author: John Ralls <jralls at ceridwen.us>
Date:   Thu Jul 23 15:29:40 2026 -0700

    Remove WebKit1 files and references.
    
    Replaced by WebView2.

diff --git a/common/config.h.cmake.in b/common/config.h.cmake.in
index 3673ddfeda..e9db63c441 100644
--- a/common/config.h.cmake.in
+++ b/common/config.h.cmake.in
@@ -313,8 +313,7 @@
 /* Define to 1 if you need to in order for `stat' and other things to work. */
 #cmakedefine _POSIX_SOURCE 1
 
-/* Definitions for what WEBKIT version we're compiling against: */
+/* Definitions for what WebView type we're building: */
 #cmakedefine WEBKIT2 1
-#cmakedefine WEBKIT1 1
 #cmakedefine WEBVIEW2 1
 
diff --git a/gnucash/gnome/gnc-plugin-page-report.cpp b/gnucash/gnome/gnc-plugin-page-report.cpp
index 71dc73c92f..1541de4ed0 100644
--- a/gnucash/gnome/gnc-plugin-page-report.cpp
+++ b/gnucash/gnome/gnc-plugin-page-report.cpp
@@ -127,7 +127,6 @@ typedef struct GncPluginPageReportPrivate
     /// the gnc_html abstraction this PluginPage contains
 //        gnc_html *html;
     GncHtml *html;
-    gboolean webkit2;
 
     /// the container the above HTML widget is in.
     GtkContainer *container;
@@ -519,11 +518,6 @@ gnc_plugin_page_report_create_widget( GncPluginPage *page )
     report = GNC_PLUGIN_PAGE_REPORT(page);
     priv = GNC_PLUGIN_PAGE_REPORT_GET_PRIVATE(report);
 
-#ifndef WEBKIT1
-    /* Hide the ExportPdf action for Webkit2 */
-    priv->webkit2 = TRUE;
-#endif
-
     topLvl = gnc_ui_get_main_window (nullptr);
 //        priv->html = gnc_html_new( topLvl );
     priv->html = gnc_html_factory_create_html();
@@ -1255,8 +1249,6 @@ gnc_plugin_page_report_menu_update (GncPluginPage *plugin_page,
 static void
 gnc_plugin_page_report_menu_updates (GncPluginPage *plugin_page)
 {
-    GncPluginPageReportPrivate *priv;
-    GncPluginPageReport *report;
     GncMainWindow *window;
     action_toolbar_labels tooltip_list[3];
     GAction *action;
@@ -1269,9 +1261,6 @@ gnc_plugin_page_report_menu_updates (GncPluginPage *plugin_page)
         _("Add the current report's configuration to the 'Reports->Saved Report Configurations' menu. "
           "The report configuration will be saved in the file %s."), saved_reports_path);
 
-    report = GNC_PLUGIN_PAGE_REPORT(plugin_page);
-    priv = GNC_PLUGIN_PAGE_REPORT_GET_PRIVATE(report);
-
     window = (GncMainWindow*)gnc_plugin_page_get_window (GNC_PLUGIN_PAGE(plugin_page));
 
     tooltip_list[0] = { "ReportSaveAction", N_("Save _Report Configuration"), report_save_str };
@@ -1284,11 +1273,8 @@ gnc_plugin_page_report_menu_updates (GncPluginPage *plugin_page)
     action = gnc_main_window_find_action (window, "FilePrintAction");
     g_simple_action_set_enabled (G_SIMPLE_ACTION(action), true);
 
-    if (priv->webkit2)
-    {
-        GtkWidget *pdf_item = gnc_main_window_menu_find_menu_item (window, "FilePrintPDFAction");
-        gtk_widget_hide (pdf_item);
-    }
+    GtkWidget *pdf_item = gnc_main_window_menu_find_menu_item (window, "FilePrintPDFAction");
+    gtk_widget_hide (pdf_item);
     g_free (saved_reports_path);
     g_free (report_save_str);
     g_free (report_saveas_str);
@@ -1305,7 +1291,6 @@ gnc_plugin_page_report_constr_init (GncPluginPageReport *plugin_page, gint repor
     DEBUG("property reportId=%d", reportId);
     priv = GNC_PLUGIN_PAGE_REPORT_GET_PRIVATE(plugin_page);
     priv->reportId = reportId;
-    priv->webkit2 = FALSE;
 
     gnc_plugin_page_report_setup( GNC_PLUGIN_PAGE(plugin_page));
 
@@ -2008,11 +1993,7 @@ gnc_plugin_page_report_print_cb (GSimpleAction *simple,
 
     //g_warning("Setting job name=%s", job_name);
 
-#ifdef WEBKIT1
-    gnc_html_print (priv->html, job_name, FALSE);
-#else
     gnc_html_print (priv->html, job_name);
-#endif
 
     g_free (job_name);
 }
@@ -2054,11 +2035,7 @@ gnc_plugin_page_report_exportpdf_cb (GSimpleAction *simple,
 
     //g_warning("Setting job name=%s", job_name);
 
-#ifdef WEBKIT1
-    gnc_html_print (priv->html, job_name, TRUE);
-#else
     gnc_html_print (priv->html, job_name);
-#endif
 
     if (owner)
     {
diff --git a/gnucash/html/CMakeLists.txt b/gnucash/html/CMakeLists.txt
index ae53f42977..549a1af572 100644
--- a/gnucash/html/CMakeLists.txt
+++ b/gnucash/html/CMakeLists.txt
@@ -27,15 +27,11 @@ set (html_SOURCES
 if (WEBVIEW2)
   list(APPEND html_HEADERS gnc-html-webview2.hpp)
   list(APPEND html_SOURCES gnc-html-webview2.cpp)
-  set(html_EXTRA_DIST gnc-html-webkit1.hpp gnc-html-webkit1.cpp gnc-html-webkit2.hpp gnc-html-webkit2.cpp)
-elseif (WEBKIT1)
-  list(APPEND html_HEADERS gnc-html-webkit1.hpp)
-  list(APPEND html_SOURCES gnc-html-webkit1.cpp)
-  set(html_EXTRA_DIST gnc-html-webkit2.hpp gnc-html-webkit2.cpp gnc-html-webview2.hpp gnc-html-webview2.cpp)
+  set(html_EXTRA_DIST gnc-html-webkit2.hpp gnc-html-webkit2.cpp)
 else ()
   list(APPEND html_HEADERS gnc-html-webkit2.hpp)
   list(APPEND html_SOURCES gnc-html-webkit2.cpp)
-  set(html_EXTRA_DIST gnc-html-webkit1.hpp gnc-html-webkit1.cpp gnc-html-webview2.hpp gnc-html-webview2.cpp)
+  set(html_EXTRA_DIST gnc-html-webview2.hpp gnc-html-webview2.cpp)
 endif()
 
 
diff --git a/gnucash/html/gnc-html-webkit.hpp b/gnucash/html/gnc-html-webkit.hpp
index b567c70c00..f2b9803f8f 100644
--- a/gnucash/html/gnc-html-webkit.hpp
+++ b/gnucash/html/gnc-html-webkit.hpp
@@ -23,8 +23,6 @@
 #include <config.h>
 #if defined(WEBVIEW2)
 #include "gnc-html-webview2.hpp"
-#elif defined(WEBKIT1)
-#include "gnc-html-webkit1.hpp"
 #else
 #include "gnc-html-webkit2.hpp"
 #endif
diff --git a/gnucash/html/gnc-html-webkit1.cpp b/gnucash/html/gnc-html-webkit1.cpp
deleted file mode 100644
index a7c9fa618f..0000000000
--- a/gnucash/html/gnc-html-webkit1.cpp
+++ /dev/null
@@ -1,1362 +0,0 @@
-/********************************************************************
- * gnc-html-webkit.c -- gnucash report renderer using webkit        *
- *                                                                  *
- * Copyright (C) 2000 Bill Gribble <grib at billgribble.com>           *
- * Copyright (C) 2001 Linas Vepstas <linas at linas.org>               *
- * Copyright (C) 2009 Phil Longstaff <plongstaff at rogers.com>        *
- *                                                                  *
- * This program is free software; you can redistribute it and/or    *
- * modify it under the terms of the GNU General Public License as   *
- * published by the Free Software Foundation; either version 2 of   *
- * the License, or (at your option) any later version.              *
- *                                                                  *
- * This program is distributed in the hope that it will be useful,  *
- * but WITHOUT ANY WARRANTY; without even the implied warranty of   *
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the    *
- * GNU General Public License for more details.                     *
- *                                                                  *
- * You should have received a copy of the GNU General Public License*
- * along with this program; if not, contact:                        *
- *                                                                  *
- * Free Software Foundation           Voice:  +1-617-542-5942       *
- * 51 Franklin Street, Fifth Floor    Fax:    +1-617-542-2652       *
- * Boston, MA  02110-1301,  USA       gnu at gnu.org                   *
- ********************************************************************/
-
-#include <config.h>
-#include "platform.h"
-
-#include <gtk/gtk.h>
-#include <glib/gi18n.h>
-#include <glib/gstdio.h>
-#include <sys/types.h>
-#include <sys/stat.h>
-#include <cstdlib>
-#include <string>
-#include <cerrno>
-#include <fcntl.h>
-#include <unistd.h>
-#include <regex.h>
-
-#include <webkit/webkit.h>
-
-#include "Account.h"
-#include "gnc-prefs.h"
-#include "gnc-gui-query.h"
-#include "gnc-engine.h"
-#include "gnc-html.h"
-#include "gnc-html-webkit.hpp"
-#include "gnc-html-history.h"
-#include "print-session.h"
-
-G_DEFINE_TYPE(GncHtmlWebkit, gnc_html_webkit, GNC_TYPE_HTML )
-
-static void gnc_html_webkit_dispose( GObject* obj );
-static void gnc_html_webkit_finalize( GObject* obj );
-
-#define GNC_HTML_WEBKIT_GET_PRIVATE(o) (GNC_HTML_WEBKIT(o)->priv)
-
-#include "gnc-html-webkit-p.hpp"
-
-/* indicates the debugging module that this .o belongs to.  */
-static QofLogModule log_module = GNC_MOD_HTML;
-
-/* hashes an HTML <object classid="ID"> classid to a handler function */
-extern GHashTable* gnc_html_object_handlers;
-
-/* hashes handlers for loading different URLType data */
-extern GHashTable* gnc_html_stream_handlers;
-
-/* hashes handlers for handling different URLType data */
-extern GHashTable* gnc_html_url_handlers;
-
-static char error_404_format[] = "<html><body><h3>%s</h3><p>%s</body></html>";
-static char error_404_title[] = N_("Not found");
-static char error_404_body[] = N_("The specified URL could not be loaded.");
-
-#define BASE_URI_NAME "base-uri"
-#define GNC_PREF_RPT_DFLT_ZOOM "default-zoom"
-
-static WebKitNavigationResponse webkit_navigation_requested_cb(
-    WebKitWebView* web_view,
-    WebKitWebFrame* frame,
-    WebKitNetworkRequest* request,
-    gpointer user_data );
-static gboolean webkit_on_load_error (WebKitWebView *web_view,
-                                      WebKitWebFrame *web_frame, gchar *uri,
-                                      GError *error, gpointer data);
-static void webkit_resource_load_error (WebKitWebView *web_view,
-                                        WebKitWebFrame *web_frame,
-                                        WebKitWebResource *resource,
-                                        GError *error, gpointer data);
-static void webkit_on_url_cb( WebKitWebView* web_view, gchar* title, gchar* url,
-                              gpointer data );
-static gchar* handle_embedded_object( GncHtmlWebkit* self, gchar* html_str );
-#if 0
-static void gnc_html_set_base_cb( GtkHTML* gtkhtml, const gchar* base, gpointer data );
-static void gnc_html_link_clicked_cb( GtkHTML* html, const gchar* url, gpointer data );
-static gboolean gnc_html_object_requested_cb( GtkHTML* html, GtkHTMLEmbedded* eb,
-        gpointer data );
-#endif
-#if 0 /* Not Used */
-static int gnc_html_button_press_cb( GtkWidget* widg, GdkEventButton* event,
-                                     gpointer user_data );
-#endif /* Not Used */
-static void impl_webkit_show_url( GncHtml* self, URLType type,
-                                  const gchar* location, const gchar* label,
-                                  gboolean new_window_hint );
-static void impl_webkit_show_data( GncHtml* self, const gchar* data, int datalen );
-static void impl_webkit_reload( GncHtml* self, gboolean force_rebuild );
-static void impl_webkit_copy_to_clipboard( GncHtml* self );
-static gboolean impl_webkit_export_to_file( GncHtml* self, const gchar* filepath );
-static void impl_webkit_print( GncHtml* self, const gchar* jobname, gboolean export_pdf );
-static void impl_webkit_cancel( GncHtml* self );
-static void impl_webkit_set_parent( GncHtml* self, GtkWindow* parent );
-static void impl_webkit_default_zoom_changed(gpointer prefs, gchar *pref, gpointer user_data);
-
-static void
-gnc_html_webkit_init( GncHtmlWebkit* self )
-{
-    PangoFontDescription *font_desc = nullptr;
-
-    auto new_priv = static_cast<GncHtmlWebkitPrivate*>(
-        g_realloc( GNC_HTML(self)->priv, sizeof(GncHtmlWebkitPrivate) )
-    );
-    auto priv = self->priv = new_priv;
-    GNC_HTML(self)->priv = reinterpret_cast<GncHtmlPrivate*>(priv);
-
-    priv->html_string = nullptr;
-    priv->web_view = WEBKIT_WEB_VIEW(webkit_web_view_new());
-
-    /* Get the default font family from GtkStyleContext of a GtkWidget(priv->web_view). */
-    auto stylecontext = gtk_widget_get_style_context (GTK_WIDGET(priv->web_view));
-    gtk_style_context_get (stylecontext, gtk_widget_get_state_flags (GTK_WIDGET(priv->web_view)),
-                           "font", &font_desc, nullptr);
-
-    const char *default_font_family = pango_font_description_get_family (font_desc);
-    pango_font_description_free (font_desc);
-
-    /* Set default webkit settings */
-    auto webkit_settings = webkit_web_view_get_settings (priv->web_view);
-    g_object_set (G_OBJECT(webkit_settings), "default-encoding", "utf-8", nullptr);
-    if (default_font_family == nullptr)
-    {
-        PWARN("webkit_settings: Cannot get default font family.");
-    }
-    else
-    {
-        g_object_set (G_OBJECT(webkit_settings),
-                      "default-font-family", default_font_family,
-                      nullptr);
-        PINFO("webkit_settings: Set default font to [%s]", default_font_family);
-    }
-    /* Scale everything up */
-    gdouble zoom = gnc_prefs_get_float (GNC_PREFS_GROUP_GENERAL_REPORT, GNC_PREF_RPT_DFLT_ZOOM);
-    webkit_web_view_set_full_content_zoom (priv->web_view, TRUE);
-    webkit_web_view_set_zoom_level (priv->web_view, zoom);
-
-
-    gtk_container_add( GTK_CONTAINER(priv->base.container),
-                       GTK_WIDGET(priv->web_view) );
-
-    g_object_ref_sink( priv->base.container );
-
-    /* signals */
-    g_signal_connect( priv->web_view, "navigation-requested",
-                      G_CALLBACK(webkit_navigation_requested_cb),
-                      self);
-
-    g_signal_connect( priv->web_view, "hovering-over-link",
-                      G_CALLBACK(webkit_on_url_cb),
-                      self );
-
-#if 0
-    g_signal_connect( priv->html, "set_base",
-                      G_CALLBACK(gnc_html_set_base_cb),
-                      self);
-
-    g_signal_connect(priv->html, "link_clicked",
-                     G_CALLBACK(gnc_html_link_clicked_cb),
-                     self);
-
-    g_signal_connect (priv->html, "object_requested",
-                      G_CALLBACK (gnc_html_object_requested_cb),
-                      self);
-
-    g_signal_connect (priv->html, "button_press_event",
-                      G_CALLBACK (gnc_html_button_press_cb),
-                      self);
-
-    g_signal_connect (priv->html, "submit",
-                      G_CALLBACK(gnc_html_submit_cb),
-                      self);
-#endif
-    g_signal_connect (priv->web_view, "load-error",
-                      G_CALLBACK (webkit_on_load_error),
-                      self);
-
-    g_signal_connect (priv->web_view, "resource-load-failed",
-                      G_CALLBACK (webkit_resource_load_error),
-                      self);
-
-    gnc_prefs_register_cb (GNC_PREFS_GROUP_GENERAL_REPORT,
-            GNC_PREF_RPT_DFLT_ZOOM,
-            reinterpret_cast<gpointer>(impl_webkit_default_zoom_changed),
-            self);
-
-    LEAVE("retval %p", self);
-}
-
-static void
-gnc_html_webkit_class_init( GncHtmlWebkitClass* klass )
-{
-    GObjectClass* gobject_class = G_OBJECT_CLASS(klass);
-    GncHtmlClass* html_class = GNC_HTML_CLASS(klass);
-
-    gobject_class->dispose = gnc_html_webkit_dispose;
-    gobject_class->finalize = gnc_html_webkit_finalize;
-
-    html_class->show_url = impl_webkit_show_url;
-    html_class->show_data = impl_webkit_show_data;
-    html_class->reload = impl_webkit_reload;
-    html_class->copy_to_clipboard = impl_webkit_copy_to_clipboard;
-    html_class->export_to_file = impl_webkit_export_to_file;
-    html_class->print = impl_webkit_print;
-    html_class->cancel = impl_webkit_cancel;
-    html_class->set_parent = impl_webkit_set_parent;
-}
-
-static void
-gnc_html_webkit_dispose( GObject* obj )
-{
-    GncHtmlWebkit* self = GNC_HTML_WEBKIT(obj);
-    GncHtmlWebkitPrivate* priv = GNC_HTML_WEBKIT_GET_PRIVATE(self);
-
-    if ( priv->web_view != nullptr )
-    {
-        gtk_container_remove( GTK_CONTAINER(priv->base.container),
-                              GTK_WIDGET(priv->web_view) );
-        priv->web_view = nullptr;
-    }
-
-    if ( priv->html_string != nullptr )
-    {
-        g_free( priv->html_string );
-        priv->html_string = nullptr;
-    }
-
-    gnc_prefs_remove_cb_by_func (GNC_PREFS_GROUP_GENERAL_REPORT,
-            GNC_PREF_RPT_DFLT_ZOOM,
-            reinterpret_cast<gpointer>(
-                impl_webkit_default_zoom_changed
-            ),
-            obj);
-
-    G_OBJECT_CLASS(gnc_html_webkit_parent_class)->dispose( obj );
-}
-
-static void
-gnc_html_webkit_finalize( GObject* obj )
-{
-    GncHtmlWebkit* self = GNC_HTML_WEBKIT(obj);
-
-    self->priv = nullptr;
-
-    G_OBJECT_CLASS(gnc_html_webkit_parent_class)->finalize( obj );
-}
-
-/*****************************************************************************/
-
-static char*
-extract_base_name(URLType type, const gchar* path)
-{
-    constexpr gchar       machine_rexp[] = "^(//[^/]*)/*(/.*)?$";
-    constexpr gchar       path_rexp[] = "^/*(.*)/+([^/]*)$";
-    regex_t    compiled_m, compiled_p;
-    constexpr size_t MATCH_LEN = 4;
-    regmatch_t match[MATCH_LEN];
-    gchar       * machine = nullptr, * location = nullptr, * base = nullptr;
-    gchar       * basename = nullptr;
-
-    DEBUG(" ");
-    if (!path) return nullptr;
-
-    regcomp(&compiled_m, machine_rexp, REG_EXTENDED);
-    regcomp(&compiled_p, path_rexp, REG_EXTENDED);
-
-    if (!g_strcmp0 (type, URL_TYPE_HTTP) ||
-            !g_strcmp0 (type, URL_TYPE_SECURE) ||
-            !g_strcmp0 (type, URL_TYPE_FTP))
-    {
-
-        /* step 1: split the machine name away from the path
-         * components */
-        if (!regexec(&compiled_m, path, MATCH_LEN, match, 0))
-        {
-            /* $1 is the machine name */
-            if (match[1].rm_so != -1)
-            {
-                machine = g_strndup(path + match[1].rm_so,
-                                    match[1].rm_eo - match[1].rm_so);
-            }
-            /* $2 is the path */
-            if (match[2].rm_so != -1)
-            {
-                location = g_strndup(path + match[2].rm_so,
-                                     match[2].rm_eo - match[2].rm_so);
-            }
-        }
-
-    }
-    else
-    {
-        location = g_strdup(path);
-    }
-    /* step 2: split up the path into prefix and file components */
-    if (location)
-    {
-        if (!regexec(&compiled_p, location, 4, match, 0))
-        {
-            if (match[1].rm_so != -1)
-            {
-                base = g_strndup(location + match[1].rm_so,
-                                 match[1].rm_eo - match[1].rm_so);
-            }
-            else
-            {
-                base = nullptr;
-            }
-        }
-    }
-
-    regfree(&compiled_m);
-    regfree(&compiled_p);
-
-    if (machine)
-    {
-        if (base && (strlen(base) > 0))
-        {
-            basename = g_strconcat(machine, "/", base, "/", nullptr);
-        }
-        else
-        {
-            basename = g_strconcat(machine, "/", nullptr);
-        }
-    }
-    else
-    {
-        if (base && (strlen(base) > 0))
-        {
-            basename = g_strdup(base);
-        }
-        else
-        {
-            basename = nullptr;
-        }
-    }
-
-    g_free(machine);
-    g_free(base);
-    g_free(location);
-    return basename;
-}
-
-static gboolean
-http_allowed()
-{
-    return TRUE;
-}
-
-static gboolean
-https_allowed()
-{
-    return TRUE;
-}
-
-static gchar*
-handle_embedded_object( GncHtmlWebkit* self, gchar* html_str )
-{
-    // Find the <object> tag and get the classid from it.  This will provide the correct
-    // object callback handler.  Pass the <object> entity text to the handler.  What should
-    // come back is embedded image information.
-    gchar* remainder_str = html_str;
-    gchar* object_tag;
-    gchar* end_object_tag;
-    gchar* object_contents;
-    gchar* html_str_start = nullptr;
-    gchar* html_str_middle;
-    gchar* html_str_result = nullptr;
-    gchar* classid_start;
-    gchar* classid_end;
-    gchar* classid_str;
-    gchar* new_chunk;
-    GncHTMLObjectCB h;
-
-    object_tag = g_strstr_len( remainder_str, -1, "<object classid=" );
-    while (object_tag)
-    {
-
-        classid_start = object_tag + strlen( "<object classid=" ) + 1;
-        classid_end = g_strstr_len( classid_start, -1, "\"" );
-        classid_str = g_strndup( classid_start, (classid_end - classid_start) );
-
-        end_object_tag = g_strstr_len( object_tag, -1, "</object>" );
-        if ( end_object_tag == nullptr )
-        {
-            /*  Hmmm... no object end tag
-                Return the original html string because we can't properly parse it */
-            g_free (classid_str);
-            g_free (html_str_result);
-            return g_strdup (html_str);
-        }
-        end_object_tag += strlen( "</object>" );
-        object_contents = g_strndup( object_tag, (end_object_tag - object_tag) );
-
-        const gpointer p = g_hash_table_lookup( gnc_html_object_handlers, classid_str );
-        h = reinterpret_cast<GncHTMLObjectCB>(p);
-        if ( h != nullptr )
-        {
-            (void)h( GNC_HTML(self), object_contents, &html_str_middle );
-        }
-        else
-        {
-            html_str_middle = g_strdup_printf( "No handler found for classid \"%s\"", classid_str );
-        }
-
-        html_str_start = html_str_result;
-        new_chunk = g_strndup (remainder_str, (object_tag - remainder_str));
-        if (!html_str_start)
-            html_str_result = g_strconcat (new_chunk, html_str_middle, nullptr);
-        else
-            html_str_result = g_strconcat (html_str_start, new_chunk, html_str_middle, nullptr);
-
-        g_free( html_str_start );
-        g_free( new_chunk );
-        g_free( html_str_middle );
-
-        remainder_str = end_object_tag;
-        object_tag = g_strstr_len( remainder_str, -1, "<object classid=" );
-    }
-
-    if (html_str_result)
-    {
-        html_str_start =  html_str_result;
-        html_str_result = g_strconcat (html_str_start, remainder_str, nullptr);
-        g_free (html_str_start);
-    }
-    else
-        html_str_result = g_strdup (remainder_str);
-
-    return html_str_result;
-}
-
-/********************************************************************
- * load_to_stream : actually do the work of loading the HTML
- * or binary data referenced by a URL and feeding it into the webkit
- * widget.
- ********************************************************************/
-
-static gboolean
-load_to_stream( GncHtmlWebkit* self, URLType type,
-                const gchar* location, const gchar* label )
-{
-    gchar* fdata = nullptr;
-    int fdata_len = 0;
-    GncHtmlWebkitPrivate* priv = GNC_HTML_WEBKIT_GET_PRIVATE(self);
-
-    DEBUG( "type %s, location %s, label %s", type ? type : "(null)",
-           location ? location : "(null)", label ? label : "(null)");
-
-    g_return_val_if_fail( self != nullptr, FALSE );
-
-    if ( gnc_html_stream_handlers != nullptr )
-    {
-        const gpointer p = g_hash_table_lookup( gnc_html_stream_handlers, type );
-        GncHTMLStreamCB stream_handler = reinterpret_cast<GncHTMLStreamCB>(p);
-        if ( stream_handler )
-        {
-            GncHtml *weak_html = GNC_HTML(self);
-
-            g_object_add_weak_pointer(G_OBJECT(self), (gpointer *)(&weak_html));
-
-            bool ok = stream_handler(location, &fdata, &fdata_len);
-
-            if (!weak_html) // will be nullptr if self has been destroyed
-            {
-                g_free (fdata);
-                return FALSE;
-            }
-            else
-            {
-                g_object_remove_weak_pointer(G_OBJECT(self),
-                                            (gpointer*)(&weak_html));
-            }
-
-            if (ok)
-            {
-                fdata = fdata ? fdata : g_strdup("");
-
-                // Until webkitgtk supports download requests, look for "<object
-                // classid=" indicating the beginning of an embedded graph.  If
-                // found, handle it
-                if (g_strstr_len(fdata, -1, "<object classid=") != nullptr)
-                {
-                    gchar *new_fdata;
-                    new_fdata = handle_embedded_object(self, fdata);
-                    g_free(fdata);
-                    fdata = new_fdata;
-                }
-
-                // Save a copy for export purposes
-                if (priv->html_string != nullptr)
-                {
-                    g_free(priv->html_string);
-                }
-                priv->html_string = g_strdup(fdata);
-                impl_webkit_show_data(GNC_HTML(self), fdata, strlen(fdata));
-            }
-            else
-            {
-                fdata = fdata ? fdata :
-                        g_strdup_printf( error_404_format,
-                                         _(error_404_title), _(error_404_body) );
-                webkit_web_view_load_html_string( priv->web_view, fdata, BASE_URI_NAME );
-            }
-
-            g_free( fdata );
-
-            if ( label )
-            {
-                while ( gtk_events_pending() )
-                {
-                    gtk_main_iteration();
-                }
-                /* No action required: Webkit jumps to the anchor on its own. */
-            }
-
-            return TRUE;
-        }
-    }
-
-    do
-    {
-        if ( !g_strcmp0( type, URL_TYPE_SECURE ) ||
-                !g_strcmp0( type, URL_TYPE_HTTP ) )
-        {
-
-            if ( !g_strcmp0( type, URL_TYPE_SECURE ) )
-            {
-                if ( !https_allowed() )
-                {
-                    gnc_error_dialog (GTK_WINDOW (priv->base.parent), "%s",
-                                      _("Secure HTTP access is disabled. "
-                                        "You can enable it in the Network section of "
-                                        "the Preferences dialog."));
-                    break;
-                }
-            }
-
-            if ( !http_allowed() )
-            {
-                gnc_error_dialog (GTK_WINDOW (priv->base.parent), "%s",
-                                  _("Network HTTP access is disabled. "
-                                    "You can enable it in the Network section of "
-                                    "the Preferences dialog."));
-            }
-            else
-            {
-                gnc_build_url( type, location, label );
-            }
-
-        }
-        else
-        {
-            PWARN( "load_to_stream for inappropriate type\n"
-                   "\turl = '%s#%s'\n",
-                   location ? location : "(null)",
-                   label ? label : "(null)" );
-            fdata = g_strdup_printf( error_404_format,
-                                     _(error_404_title), _(error_404_body) );
-            webkit_web_view_load_html_string( priv->web_view, fdata, BASE_URI_NAME );
-            g_free( fdata );
-        }
-
-    }
-    while ( false );
-    return TRUE;
-}
-
-#if 0
-/********************************************************************
- * gnc_html_link_clicked_cb - called when user left-clicks on html
- * anchor.
- ********************************************************************/
-
-static void
-gnc_html_link_clicked_cb( GtkHTML* html, const gchar* url, gpointer data )
-{
-    URLType type;
-    gchar* location = nullptr;
-    gchar* label = nullptr;
-    GncHtmlWebkit* self = GNC_HTML_WEBKIT(data);
-
-    DEBUG("Clicked %s", url);
-    type = gnc_html_parse_url( GNC_HTML(self), url, &location, &label );
-    gnc_html_show_url( GNC_HTML(self), type, location, label, 0 );
-    g_free( location );
-    g_free( label );
-}
-#endif
-
-/********************************************************************
- * webkit_navigation_requested_cb - called when a URL needs to be
- * loaded within the loading of a page (embedded image).
- ********************************************************************/
-
-static WebKitNavigationResponse
-webkit_navigation_requested_cb( WebKitWebView* web_view, WebKitWebFrame* frame,
-                                WebKitNetworkRequest* request,
-                                gpointer data )
-{
-    gchar* location = nullptr;
-    gchar* label = nullptr;
-    GncHtmlWebkit* self = GNC_HTML_WEBKIT(data);
-    const gchar* url = webkit_network_request_get_uri( request );
-
-    ENTER( "requesting %s", url );
-    if ( strcmp( url, BASE_URI_NAME ) == 0 )
-    {
-        LEAVE("URI is %s", BASE_URI_NAME);
-        return WEBKIT_NAVIGATION_RESPONSE_ACCEPT;
-    }
-
-    URLType type = gnc_html_parse_url( GNC_HTML(self), url, &location, &label );
-    if ( strcmp( type, "file" ) == 0 )
-    {
-        LEAVE("URI type is 'file'");
-        return WEBKIT_NAVIGATION_RESPONSE_ACCEPT;
-    }
-    gnc_html_show_url( GNC_HTML(self), type, location, label, 0 );
-//	load_to_stream( self, type, location, label );
-    g_free( location );
-    g_free( label );
-
-    LEAVE("");
-    return WEBKIT_NAVIGATION_RESPONSE_IGNORE;
-}
-
-static gboolean
-webkit_on_load_error (WebKitWebView *web_view, WebKitWebFrame *web_frame,
-                      gchar *uri, GError *error, gpointer data)
-{
-     PERR ("WebKit load of %s failed due to %s\n", uri, error->message);
-     return FALSE;
-}
-
-static void
-webkit_resource_load_error (WebKitWebView *web_view, WebKitWebFrame *web_frame,
-                            WebKitWebResource *resource, GError *error,
-                            gpointer data)
-{
-     const gchar *uri = webkit_web_resource_get_uri (resource);
-     const gchar *type = webkit_web_resource_get_mime_type (resource);
-     PERR ("WebKit load of resource %s, type %s, failed due to %s\n",
-              uri, type, error->message);
-}
-
-#if 0
-/********************************************************************
- * gnc_html_object_requested_cb - called when an applet needs to be
- * loaded.
- ********************************************************************/
-
-static gboolean
-gnc_html_object_requested_cb( GtkHTML* html, GtkHTMLEmbedded* eb,
-                              gpointer data )
-{
-    GncHtmlWebkit* self = GNC_HTML_WEBKIT(data);
-
-    DEBUG( " " );
-    if ( !eb || !(eb->classid) || !gnc_html_object_handlers ) return FALSE;
-
-    auto h = g_hash_table_lookup( gnc_html_object_handlers, eb->classid );
-    if ( h )
-    {
-        return h( GNC_HTML(self), eb, data );
-    }
-    else
-    {
-        return FALSE;
-    }
-}
-#endif
-
-/********************************************************************
- * webkit_on_url_cb - called when user rolls over html anchor
- ********************************************************************/
-
-static void
-webkit_on_url_cb( WebKitWebView* web_view, gchar* title, gchar* url, gpointer data )
-{
-    GncHtmlWebkit* self = GNC_HTML_WEBKIT(data);
-    GncHtmlWebkitPrivate* priv = GNC_HTML_WEBKIT_GET_PRIVATE(self);
-
-    DEBUG( "Rollover %s", url ? url : "(null)" );
-    g_free( priv->base.current_link );
-    priv->base.current_link = g_strdup( url );
-    if ( priv->base.flyover_cb )
-    {
-        (priv->base.flyover_cb)( GNC_HTML(self), url, priv->base.flyover_cb_data );
-    }
-}
-
-#if 0
-/********************************************************************
- * gnc_html_set_base_cb
- ********************************************************************/
-
-static void
-gnc_html_set_base_cb( GtkHTML* gtkhtml, const gchar* base,
-                      gpointer data )
-{
-    GncHtmlWebkit* self = GNC_HTML_WEBKIT(data);
-    GncHtmlWebkitPrivate* priv = GNC_HTML_WEBKIT_GET_PRIVATE(self);
-    gchar* location = nullptr;
-    gchar* label = nullptr;
-
-    DEBUG( "Setting base location to %s", base );
-    URLType type = gnc_html_parse_url( GNC_HTML(self), base, &location, &label );
-
-    g_free( priv->base.base_location );
-    g_free( label );
-
-    priv->base.base_type = type;
-    priv->base.base_location = location;
-}
-#endif
-
-/********************************************************************
- * gnc_html_button_press_cb
- * mouse button callback (if any)
- ********************************************************************/
-
-#if 0 /* Not Used */
-static int
-gnc_html_button_press_cb( GtkWidget* widg, GdkEventButton* event,
-                          gpointer user_data )
-{
-    GncHtmlWebkit* self = GNC_HTML_WEBKIT(user_data);
-    GncHtmlWebkitPrivate* priv = GNC_HTML_WEBKIT_GET_PRIVATE(self);
-
-    DEBUG( "Button Press" );
-    if ( priv->base.button_cb != nullptr )
-    {
-        (priv->base.button_cb)( GNC_HTML(self), event, priv->base.button_cb_data );
-        return TRUE;
-    }
-    else
-    {
-        return FALSE;
-    }
-}
-#endif /* Not Used */
-
-/********************************************************************
- * gnc_html_open_scm
- * insert some scheme-generated HTML
- ********************************************************************/
-
-static void
-gnc_html_open_scm( GncHtmlWebkit* self, const gchar * location,
-                   const gchar * label, int newwin )
-{
-    PINFO("location='%s'", location ? location : "(null)");
-}
-
-
-/********************************************************************
- * gnc_html_show_data
- * display some HTML that the creator of the gnc-html got from
- * somewhere.
- ********************************************************************/
-
-static void
-impl_webkit_show_data( GncHtml* self, const gchar* data, int datalen )
-{
-#define TEMPLATE_REPORT_FILE_NAME "gnc-report-XXXXXX.html"
-    g_return_if_fail( self != nullptr );
-    g_return_if_fail( GNC_IS_HTML_WEBKIT(self) );
-
-    ENTER( "datalen %d, data %20.20s", datalen, data );
-
-    auto priv = GNC_HTML_WEBKIT_GET_PRIVATE(self);
-
-    /* Export the HTML to a file and load the file URI.   On Linux, this seems to get around some
-       security problems (otherwise, it can complain that embedded images aren't permitted to be
-       viewed because they are local resources).  On Windows, this allows the embedded images to
-       be viewed (maybe for the same reason as on Linux, but I haven't found where it puts those
-       messages. */
-    gchar *filename = g_build_filename(g_get_tmp_dir(), TEMPLATE_REPORT_FILE_NAME, (gchar *)nullptr);
-    int fd = g_mkstemp( filename );
-    impl_webkit_export_to_file( self, filename );
-    close( fd );
-#ifdef G_OS_WIN32
-    gchar *uri = g_strdup_printf( "file:///%s", filename );
-#else
-    gchar *uri = g_strdup_printf( "file://%s", filename );
-#endif
-    g_free(filename);
-    DEBUG("Loading uri '%s'", uri);
-    webkit_web_view_load_uri( priv->web_view, uri );
-    g_free( uri );
-
-    LEAVE("");
-}
-
-/********************************************************************
- * gnc_html_show_url
- *
- * open a URL.  This is called when the user clicks a link or
- * for the creator of the gnc_html window to explicitly request
- * a URL.
- ********************************************************************/
-
-static void
-impl_webkit_show_url( GncHtml* self, URLType type,
-                      const gchar* location, const gchar* label,
-                      gboolean new_window_hint )
-{
-    GncHTMLUrlCB url_handler = nullptr;
-    bool new_window = false;
-    bool stream_loaded = false;
-
-    g_return_if_fail( self != nullptr );
-    g_return_if_fail( GNC_IS_HTML_WEBKIT(self) );
-    g_return_if_fail( location != nullptr );
-
-    auto priv = GNC_HTML_WEBKIT_GET_PRIVATE(self);
-
-    /* make sure it's OK to show this URL type in this window */
-    if ( new_window_hint == 0 )
-    {
-        if ( priv->base.urltype_cb )
-        {
-            new_window = !((priv->base.urltype_cb)( type ));
-        }
-    }
-    else
-    {
-        new_window = true;
-    }
-
-    if ( !new_window )
-    {
-        gnc_html_cancel( GNC_HTML(self) );
-    }
-
-    if ( gnc_html_url_handlers )
-    {
-        url_handler = reinterpret_cast<GncHTMLUrlCB>(
-            g_hash_table_lookup( gnc_html_url_handlers, type )
-        );
-    }
-
-    if ( url_handler )
-    {
-        GNCURLResult result;
-
-        result.load_to_stream = FALSE;
-        result.url_type = type;
-        result.location = nullptr;
-        result.label = nullptr;
-        result.base_type = URL_TYPE_FILE;
-        result.base_location = nullptr;
-        result.error_message = nullptr;
-        result.parent = GTK_WINDOW (priv->base.parent);
-
-        bool ok = url_handler( location, label, new_window, &result );
-        if ( !ok )
-        {
-            if ( result.error_message )
-            {
-                gnc_error_dialog (GTK_WINDOW (priv->base.parent), "%s", result.error_message );
-            }
-            else
-            {
-                /* %s is a URL (some location somewhere). */
-                gnc_error_dialog (GTK_WINDOW (priv->base.parent), _("There was an error accessing %s."), location );
-            }
-
-            if ( priv->base.load_cb )
-            {
-                priv->base.load_cb( GNC_HTML(self), result.url_type,
-                                    location, label, priv->base.load_cb_data );
-            }
-        }
-        else if ( result.load_to_stream )
-        {
-            gnc_html_history_node *hnode;
-            const char *new_location;
-            const char *new_label;
-
-            new_location = result.location ? result.location : location;
-            new_label = result.label ? result.label : label;
-            hnode = gnc_html_history_node_new( result.url_type, new_location, new_label );
-
-            gnc_html_history_append( priv->base.history, hnode );
-
-            g_free( priv->base.base_location );
-            priv->base.base_type = result.base_type;
-            priv->base.base_location =
-                g_strdup( extract_base_name( result.base_type, new_location ) );
-            DEBUG( "resetting base location to %s",
-                   priv->base.base_location ? priv->base.base_location : "(null)" );
-
-            stream_loaded = load_to_stream( GNC_HTML_WEBKIT(self),
-                                            result.url_type,
-                                            new_location, new_label );
-
-            if ( stream_loaded && priv->base.load_cb != nullptr )
-            {
-                priv->base.load_cb( GNC_HTML(self), result.url_type,
-                                    new_location, new_label, priv->base.load_cb_data );
-            }
-        }
-
-        g_free( result.location );
-        g_free( result.label );
-        g_free( result.base_location );
-        g_free( result.error_message );
-
-        return;
-    }
-
-    if ( g_strcmp0( type, URL_TYPE_SCHEME ) == 0 )
-    {
-        gnc_html_open_scm( GNC_HTML_WEBKIT(self), location, label, new_window );
-
-    }
-    else if ( g_strcmp0( type, URL_TYPE_JUMP ) == 0 )
-    {
-        /* Webkit jumps to the anchor on its own */
-    }
-    else if ( g_strcmp0( type, URL_TYPE_SECURE ) == 0 ||
-              g_strcmp0( type, URL_TYPE_HTTP ) == 0 ||
-              g_strcmp0( type, URL_TYPE_FILE ) == 0 )
-    {
-
-        do
-        {
-            if ( g_strcmp0( type, URL_TYPE_SECURE ) == 0 )
-            {
-                if ( !https_allowed() )
-                {
-                    gnc_error_dialog (GTK_WINDOW (priv->base.parent), "%s",
-                                      _("Secure HTTP access is disabled. "
-                                        "You can enable it in the Network section of "
-                                        "the Preferences dialog.") );
-                    break;
-                }
-            }
-
-            if ( g_strcmp0( type, URL_TYPE_HTTP ) == 0 )
-            {
-                if ( !http_allowed() )
-                {
-                    gnc_error_dialog (GTK_WINDOW (priv->base.parent), "%s",
-                                      _("Network HTTP access is disabled. "
-                                        "You can enable it in the Network section of "
-                                        "the Preferences dialog.") );
-                    break;
-                }
-            }
-
-            priv->base.base_type = type;
-
-            if ( priv->base.base_location != nullptr ) g_free( priv->base.base_location );
-            priv->base.base_location = extract_base_name( type, location );
-
-            /* FIXME : handle new_window = 1 */
-            gnc_html_history_append( priv->base.history,
-                                     gnc_html_history_node_new( type, location, label ) );
-            stream_loaded = load_to_stream( GNC_HTML_WEBKIT(self), type,
-                                            location, label );
-
-        }
-        while ( FALSE );
-
-    }
-    else
-    {
-        PERR( "URLType %s not supported.", type );
-    }
-
-    if ( stream_loaded && priv->base.load_cb != nullptr )
-    {
-        (priv->base.load_cb)( GNC_HTML(self), type, location, label, priv->base.load_cb_data );
-    }
-}
-
-
-/********************************************************************
- * gnc_html_reload
- * reload the current page
- * if force_rebuild is TRUE, the report is recreated, if FALSE, report
- * is reloaded by webkit
- ********************************************************************/
-
-static void
-impl_webkit_reload( GncHtml* self, gboolean force_rebuild )
-{
-    GncHtmlWebkitPrivate* priv;
-
-    g_return_if_fail( self != nullptr );
-    g_return_if_fail( GNC_IS_HTML_WEBKIT(self) );
-
-    priv = GNC_HTML_WEBKIT_GET_PRIVATE(self);
-
-    if ( force_rebuild )
-    {
-        gnc_html_history_node *n = gnc_html_history_get_current( priv->base.history );
-        if ( n != nullptr )
-            gnc_html_show_url( self, n->type, n->location, n->label, 0 );
-    }
-    else
-        webkit_web_view_reload( priv->web_view );
-}
-
-
-/********************************************************************
- * gnc_html_new
- * create and set up a new webkit widget.
- ********************************************************************/
-
-GncHtml*
-gnc_html_webkit_new( void )
-{
-    GncHtmlWebkit* self = static_cast<GncHtmlWebkit *>(
-        g_object_new( GNC_TYPE_HTML_WEBKIT, nullptr )
-    );
-    return GNC_HTML(self);
-}
-
-/********************************************************************
- * gnc_html_cancel
- * cancel any outstanding HTML fetch requests.
- ********************************************************************/
-
-static gboolean
-webkit_cancel_helper(gpointer key, gpointer value, gpointer user_data)
-{
-    g_free(key);
-    g_list_free((GList *)value);
-    return TRUE;
-}
-
-static void
-impl_webkit_cancel( GncHtml* self )
-{
-    GncHtmlWebkitPrivate* priv;
-
-    g_return_if_fail( self != nullptr );
-    g_return_if_fail( GNC_IS_HTML_WEBKIT(self) );
-
-    priv = GNC_HTML_WEBKIT_GET_PRIVATE(self);
-
-    g_hash_table_foreach_remove( priv->base.request_info, webkit_cancel_helper, nullptr );
-}
-
-static void
-impl_webkit_copy_to_clipboard( GncHtml* self )
-{
-    GncHtmlWebkitPrivate* priv;
-
-    g_return_if_fail( self != nullptr );
-    g_return_if_fail( GNC_IS_HTML_WEBKIT(self) );
-
-    priv = GNC_HTML_WEBKIT_GET_PRIVATE(self);
-    if ( webkit_web_view_can_copy_clipboard( priv->web_view ) )
-    {
-        webkit_web_view_copy_clipboard( priv->web_view );
-    }
-}
-
-/**************************************************************
- * gnc_html_export_to_file
- *
- * @param self GncHtmlWebkit object
- * @param filepath Where to write the HTML
- * @return TRUE if successful, FALSE if unsuccessful
- **************************************************************/
-static gboolean
-impl_webkit_export_to_file( GncHtml* self, const char *filepath )
-{
-    FILE *fh;
-    GncHtmlWebkitPrivate* priv;
-
-    g_return_val_if_fail( self != nullptr, FALSE );
-    g_return_val_if_fail( GNC_IS_HTML_WEBKIT(self), FALSE );
-    g_return_val_if_fail( filepath != nullptr, FALSE );
-
-    priv = GNC_HTML_WEBKIT_GET_PRIVATE(self);
-    if ( priv->html_string == nullptr )
-    {
-        return FALSE;
-    }
-    fh = g_fopen( filepath, "w" );
-    if ( fh != nullptr )
-    {
-        gint written;
-        gint len = strlen( priv->html_string );
-
-        written = fwrite( priv->html_string, 1, len, fh );
-        fclose (fh);
-
-        if ( written != len )
-        {
-            return FALSE;
-        }
-
-        return TRUE;
-    }
-    else
-    {
-        return FALSE;
-    }
-}
-
-/**
- * Prints the current page.
- *
- * If printing on WIN32, in order to prevent the font from being tiny, (see bug #591177),
- * A GtkPrintOperation object needs to be created so that the unit can be set, and then
- * webkit_web_frame_print_full() needs to be called to use that GtkPrintOperation.  On
- * other platforms (specifically linux - not sure about MacOSX), the version of webkit may
- * not contain the function webkit_web_frame_print_full(), so webkit_web_frame_print() is
- * called instead (the font size problem doesn't show up on linux).
- *
- * @param self HTML renderer object
- */
-static void
-impl_webkit_print( GncHtml* self, const gchar* jobname, gboolean export_pdf )
-{
-    gchar *export_filename = nullptr;
-    GncHtmlWebkitPrivate* priv;
-    WebKitWebFrame* frame;
-    GtkPrintOperation* op = gtk_print_operation_new();
-    GError* error = nullptr;
-    GtkPrintSettings *print_settings;
-
-    priv = GNC_HTML_WEBKIT_GET_PRIVATE(self);
-    frame = webkit_web_view_get_main_frame( priv->web_view );
-
-    gnc_print_operation_init( op, jobname );
-    print_settings = gtk_print_operation_get_print_settings (op);
-    if (!print_settings)
-    {
-        print_settings = gtk_print_settings_new();
-        gtk_print_operation_set_print_settings(op, print_settings);
-    }
-#ifdef G_OS_WIN32
-    gtk_print_operation_set_unit( op, GTK_UNIT_POINTS );
-#endif
-
-    // Make sure to generate a full export filename
-    if (g_str_has_suffix(jobname, ".pdf"))
-    {
-        export_filename = g_strdup(jobname);
-    }
-    else
-    {
-        export_filename = g_strconcat(jobname, ".pdf", nullptr);
-    }
-
-    // Two different modes of operation. Either export to PDF, or run the
-    // normal print dialog
-    if (export_pdf)
-    {
-        GtkWidget *dialog;
-        gint result;
-        gchar *export_dirname = nullptr;
-        gchar* basename;
-
-        // Before we save the PDF file, we always ask the user for the export
-        // file name. We will store the chosen directory in the gtk print settings
-        // as well.
-        dialog = gtk_file_chooser_dialog_new (_("Export to PDF File"),
-                                              nullptr,
-                                              GTK_FILE_CHOOSER_ACTION_SAVE,
-                                              _("_Cancel"), GTK_RESPONSE_CANCEL,
-                                              _("_Save"), GTK_RESPONSE_ACCEPT,
-                                              nullptr);
-        gtk_file_chooser_set_do_overwrite_confirmation (GTK_FILE_CHOOSER (dialog), TRUE);
-
-        // Does the jobname look like a valid full file path?
-        basename = g_path_get_basename(jobname);
-        if (strcmp(basename, jobname) != 0)
-        {
-            gchar *tmp_basename;
-            gchar *tmp_dirname = g_path_get_dirname(jobname);
-
-            if (g_file_test(tmp_dirname,
-                static_cast<GFileTest>(
-                    G_FILE_TEST_EXISTS | G_FILE_TEST_IS_DIR
-                )))
-            {
-                // Yes, the jobname starts with a directory name that actually
-                // exists. Hence we use this as output directory.
-                export_dirname = tmp_dirname;
-                tmp_dirname = nullptr;
-
-                // As the prefix part of the "jobname" is the directory path, we
-                // need to extract the suffix part for the filename.
-                tmp_basename = g_path_get_basename(export_filename);
-                g_free(export_filename);
-                export_filename = tmp_basename;
-            }
-            g_free(tmp_dirname);
-        }
-        g_free(basename);
-
-        // Set the output file name from the given jobname
-        gtk_file_chooser_set_current_name (GTK_FILE_CHOOSER(dialog), export_filename);
-
-        // Do we have a stored output directory?
-        if (!export_dirname && gtk_print_settings_has_key(print_settings, GNC_GTK_PRINT_SETTINGS_EXPORT_DIR))
-        {
-            const char* tmp_dirname = gtk_print_settings_get(print_settings,
-                                      GNC_GTK_PRINT_SETTINGS_EXPORT_DIR);
-            // Only use the directory subsequently if it exists.
-            if (g_file_test(tmp_dirname,
-                static_cast<GFileTest>(
-                    G_FILE_TEST_EXISTS | G_FILE_TEST_IS_DIR
-                )))
-            {
-                export_dirname = g_strdup(tmp_dirname);
-            }
-        }
-
-        // If we have an already existing directory, propose it now.
-        if (export_dirname)
-        {
-            gtk_file_chooser_set_current_folder(GTK_FILE_CHOOSER(dialog), export_dirname);
-        }
-        g_free(export_dirname);
-
-        result = gtk_dialog_run (GTK_DIALOG (dialog));
-        // Weird. In gtk_dialog_run, the gtk code will run a fstat() on the
-        // proposed new output filename, which of course fails with "file not
-        // found" as this file doesn't exist. It will still show a warning output
-        // in the trace file, though.
-
-        if (result == GTK_RESPONSE_ACCEPT)
-        {
-            // The user pressed "Ok", so use the file name for the actual file output.
-            gchar *dirname;
-            char *tmp = gtk_file_chooser_get_filename (GTK_FILE_CHOOSER (dialog));
-            g_free(export_filename);
-            export_filename = tmp;
-
-            // Store the directory part of the file for later
-            dirname = g_path_get_dirname(export_filename);
-            if (g_file_test(dirname,
-                static_cast<GFileTest>(
-                    G_FILE_TEST_EXISTS | G_FILE_TEST_IS_DIR
-                )))
-            {
-                gtk_print_settings_set(print_settings, GNC_GTK_PRINT_SETTINGS_EXPORT_DIR, dirname);
-            }
-            g_free(dirname);
-        }
-        gtk_widget_destroy (dialog);
-
-        if (result != GTK_RESPONSE_ACCEPT)
-        {
-            // User pressed cancel - no saving of the PDF file here.
-            g_free(export_filename);
-            g_object_unref( op );
-            return;
-        }
-
-        // This function expects the full filename including (absolute?) path
-        gtk_print_operation_set_export_filename(op, export_filename);
-
-        // Run the "Export to PDF" print operation
-        webkit_web_frame_print_full( frame, op, GTK_PRINT_OPERATION_ACTION_EXPORT, &error );
-    }
-    else
-    {
-
-        // Also store this export file name as output URI in the settings
-        if (gtk_print_settings_has_key(print_settings, GTK_PRINT_SETTINGS_OUTPUT_URI))
-        {
-            // Get the previous output URI, extract the directory part, and
-            // append the current filename.
-            const gchar *olduri = gtk_print_settings_get(print_settings, GTK_PRINT_SETTINGS_OUTPUT_URI);
-            gchar *dirname = g_path_get_dirname(olduri);
-            gchar *newuri = (g_strcmp0(dirname, ".") == 0)
-                            ? g_strdup(export_filename)
-                            : g_build_filename(dirname, export_filename, nullptr);
-            //g_warning("olduri=%s newuri=%s", olduri, newuri);
-
-            // This function expects the full filename including protocol, path, and name
-            gtk_print_settings_set(print_settings, GTK_PRINT_SETTINGS_OUTPUT_URI, newuri);
-
-            g_free(newuri);
-            g_free(dirname);
-        }
-        else
-        {
-            // No stored output URI from the print settings, so just set our export filename
-            gtk_print_settings_set(print_settings, GTK_PRINT_SETTINGS_OUTPUT_URI, export_filename);
-        }
-
-        // Run the normal printing dialog
-        webkit_web_frame_print_full( frame, op, GTK_PRINT_OPERATION_ACTION_PRINT_DIALOG, &error );
-    }
-
-    if ( error != nullptr )
-    {
-        GtkWidget* window = gtk_widget_get_toplevel( GTK_WIDGET(priv->web_view) );
-        GtkWidget* dialog = gtk_message_dialog_new( gtk_widget_is_toplevel(window) ? GTK_WINDOW(window) : nullptr,
-                            GTK_DIALOG_DESTROY_WITH_PARENT,
-                            GTK_MESSAGE_ERROR,
-                            GTK_BUTTONS_CLOSE,
-                            "%s", error->message );
-        g_error_free( error );
-
-        g_signal_connect( dialog, "response", G_CALLBACK(gtk_widget_destroy), nullptr);
-        gtk_widget_show( dialog );
-    }
-
-    // Remember to save the printing settings after this print job
-    gnc_print_operation_save_print_settings(op);
-    g_object_unref( op );
-    g_free(export_filename);
-}
-
-static void
-impl_webkit_set_parent( GncHtml* self, GtkWindow* parent )
-{
-    GncHtmlWebkitPrivate* priv;
-
-    g_return_if_fail( self != nullptr );
-    g_return_if_fail( GNC_IS_HTML_WEBKIT(self) );
-
-    priv = GNC_HTML_WEBKIT_GET_PRIVATE(self);
-    priv->base.parent = GTK_WIDGET(parent);
-}
-
-static void
-impl_webkit_default_zoom_changed(gpointer prefs, gchar *pref, gpointer user_data)
-{
-    gdouble zoom = 1.0;
-    GncHtmlWebkit* self = GNC_HTML_WEBKIT(user_data);
-    GncHtmlWebkitPrivate* priv = GNC_HTML_WEBKIT_GET_PRIVATE(self);
-
-    g_return_if_fail(user_data != nullptr);
-
-    zoom = gnc_prefs_get_float (GNC_PREFS_GROUP_GENERAL_REPORT, GNC_PREF_RPT_DFLT_ZOOM);
-    webkit_web_view_set_zoom_level (priv->web_view, zoom);
-
-}
diff --git a/gnucash/html/gnc-html-webkit1.hpp b/gnucash/html/gnc-html-webkit1.hpp
deleted file mode 100644
index e7f1033e5c..0000000000
--- a/gnucash/html/gnc-html-webkit1.hpp
+++ /dev/null
@@ -1,58 +0,0 @@
-/********************************************************************
- * gnc-html-webkit.hpp -- display html with gnc special tags          *
- * Copyright (C) 2009 Phil Longstaff <plongstaff at rogers.com>        *
- *                                                                  *
- * This program is free software; you can redistribute it and/or    *
- * modify it under the terms of the GNU General Public License as   *
- * published by the Free Software Foundation; either version 2 of   *
- * the License, or (at your option) any later version.              *
- *                                                                  *
- * This program is distributed in the hope that it will be useful,  *
- * but WITHOUT ANY WARRANTY; without even the implied warranty of   *
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the    *
- * GNU General Public License for more details.                     *
- *                                                                  *
- * You should have received a copy of the GNU General Public License*
- * along with this program; if not, contact:                        *
- *                                                                  *
- * Free Software Foundation           Voice:  +1-617-542-5942       *
- * 51 Franklin Street, Fifth Floor    Fax:    +1-617-542-2652       *
- * Boston, MA  02110-1301,  USA       gnu at gnu.org                   *
-\********************************************************************/
-
-#ifndef GNC_HTML_WEBKIT_H
-#define GNC_HTML_WEBKIT_H
-
-#include <glib-object.h>
-#include "gnc-html.h"
-
-G_BEGIN_DECLS
-
-#define GNC_TYPE_HTML_WEBKIT       (gnc_html_webkit_get_type())
-#define GNC_HTML_WEBKIT(o)         (G_TYPE_CHECK_INSTANCE_CAST ((o), GNC_TYPE_HTML_WEBKIT, GncHtmlWebkit))
-#define GNC_HTML_WEBKIT_CLASS(k)   (G_TYPE_CHECK_CLASS_CAST((k), GNC_TYPE_HTML_WEBKIT, GncHtmlWebkitClass))
-#define GNC_IS_HTML_WEBKIT(o)      (G_TYPE_CHECK_INSTANCE_TYPE((o), GNC_TYPE_HTML_WEBKIT))
-#define GNC_IS_HTML_WEBKIT_CLASS(k)   (G_TYPE_CHECK_CLASS_TYPE((k), GNC_TYPE_HTML_WEBKIT))
-#define GNC_HTML_WEBKIT_GET_CLASS(o)  (G_TYPE_INSTANCE_GET_CLASS((o), GNC_TYPE_HTML_WEBKIT, GncHtmlWebkitClass))
-
-struct GncHtmlWebkitPrivate;
-
-struct GncHtmlWebkit
-{
-    GncHtml parent_instance;
-
-    /*< private >*/
-    GncHtmlWebkitPrivate* priv;
-};
-
-struct GncHtmlWebkitClass
-{
-    GncHtmlClass parent_class;
-};
-
-GType gnc_html_webkit_get_type( void );
-GncHtml* gnc_html_webkit_new( void );
-
-G_END_DECLS
-
-#endif // GNC_HTML_WEBKIT_H
diff --git a/gnucash/html/gnc-html-webkit2.cpp b/gnucash/html/gnc-html-webkit2.cpp
index 631d729b8a..c925a89914 100644
--- a/gnucash/html/gnc-html-webkit2.cpp
+++ b/gnucash/html/gnc-html-webkit2.cpp
@@ -1040,16 +1040,7 @@ impl_webkit_export_to_file( GncHtml* self, const char *filepath )
      }
 }
 
-/* The webkit1 comment was
- * If printing on WIN32, in order to prevent the font from being tiny, (see bug
- * #591177), A GtkPrintOperation object needs to be created so that the unit can
- * be set, and then webkit_web_frame_print_full() needs to be called to use that
- * GtkPrintOperation.  On other platforms (specifically linux - not sure about
- * MacOSX), the version of webkit may not contain the function
- * webkit_web_frame_print_full(), so webkit_web_frame_print() is called instead
- * (the font size problem doesn't show up on linux).
- *
- * Webkit2 exposes only a very simple WebKitPrintOperation API. In order to
+/* Webkit2 exposes only a very simple WebKitPrintOperation API. In order to
  * implement the above if it proves still to be necessary we'll have to use
  * GtkPrintOperation instead, passing it the results of
  * webkit_web_view_get_snapshot for each page.
diff --git a/gnucash/html/gnc-html-webview2.hpp b/gnucash/html/gnc-html-webview2.hpp
index eb28f6318c..9c795541d6 100644
--- a/gnucash/html/gnc-html-webview2.hpp
+++ b/gnucash/html/gnc-html-webview2.hpp
@@ -22,8 +22,8 @@
 
 /* GncHtml backend that hosts Microsoft Edge WebView2 as a native child
  * window parented to a plain GTK widget's own natively-backed HWND
- * (see gdk_window_ensure_native()). Windows-only; selected instead of
- * gnc-html-webkit1/2 when the WEBVIEW2 build option is set. */
+ * (see gdk_window_ensure_native()). Windows-only;
+ */
 
 #ifndef GNC_HTML_WEBVIEW2_H
 #define GNC_HTML_WEBVIEW2_H
diff --git a/gnucash/html/gnc-html.cpp b/gnucash/html/gnc-html.cpp
index 198ab974cd..e2f2acb494 100644
--- a/gnucash/html/gnc-html.cpp
+++ b/gnucash/html/gnc-html.cpp
@@ -523,13 +523,8 @@ gnc_html_export_to_file( GncHtml* self, const gchar* filepath ) noexcept
         return FALSE;
     }
 }
-#ifdef WEBKIT1
-void
-gnc_html_print (GncHtml* self, const char *jobname, gboolean export_pdf) noexcept
-#else
 void
 gnc_html_print (GncHtml* self, const char *jobname) noexcept
-#endif
 {
     g_return_if_fail( self != nullptr );
     g_return_if_fail( jobname != nullptr );
@@ -537,11 +532,7 @@ gnc_html_print (GncHtml* self, const char *jobname) noexcept
 
     if ( GNC_HTML_GET_CLASS(self)->print != nullptr )
     {
-#ifdef WEBKIT1
-        GNC_HTML_GET_CLASS(self)->print (self, jobname, export_pdf);
-#else
         GNC_HTML_GET_CLASS(self)->print (self, jobname);
-#endif
     }
     else
     {
@@ -581,9 +572,6 @@ gnc_html_get_webview( GncHtml* self ) noexcept
 
     if (sw_list) // the scroll window has only one child
     {
-#ifdef WEBKIT1
-        webview = static_cast<GtkWidget *>(sw_list->data);
-#else
         GList *vp_list = gtk_container_get_children (GTK_CONTAINER(sw_list->data));
 
         if (vp_list) // the viewport has only one child
@@ -591,7 +579,6 @@ gnc_html_get_webview( GncHtml* self ) noexcept
             webview = static_cast<GtkWidget *>(vp_list->data);
             g_list_free (vp_list);
         }
-#endif
     }
     g_list_free (sw_list);
     return webview;
diff --git a/gnucash/html/gnc-html.h b/gnucash/html/gnc-html.h
index e758fd9111..6e4b9d744a 100644
--- a/gnucash/html/gnc-html.h
+++ b/gnucash/html/gnc-html.h
@@ -133,11 +133,7 @@ struct _GncHtmlClass
     void (*reload)( GncHtml* html, gboolean force_rebuild );
     void (*copy_to_clipboard)( GncHtml* html );
     gboolean (*export_to_file)( GncHtml* html, const gchar* file );
-#ifdef WEBKIT1
-  void (*print) (GncHtml* html, const gchar* jobname, gboolean export_pdf);
-#else
     void (*print) (GncHtml* html, const gchar* jobname);
-#endif
     void (*cancel)( GncHtml* html );
     URLType (*parse_url)( GncHtml* html, const gchar* url,
                           gchar** url_location, gchar** url_label );
@@ -212,25 +208,13 @@ void gnc_html_copy_to_clipboard( GncHtml* html ) NOEXCEPT;
  */
 gboolean gnc_html_export_to_file( GncHtml* html, const gchar* filename ) NOEXCEPT;
 
-#ifdef WEBKIT1
-/**
- * Prints the report.
- *
- * @param html GncHtml object
- * @param jobname A jobname for identifying the print job or to provide
- *                an output filename.
- * @param export_pdf If TRUE write a PDF file using the jobname for a
- *                   filename; otherwise put up a print dialog.
- */
-void gnc_html_print (GncHtml* html, const char* jobname, gboolean export_pdf) NOEXCEPT;
-#else
 /**
  * Prints the report.
  *
  * @param html GncHtml object
  */
 void gnc_html_print (GncHtml* html, const char* jobname) NOEXCEPT;
-#endif
+
 /**
  * Cancels the current operation
  *
diff --git a/po/POTFILES.in b/po/POTFILES.in
index 468820330e..34d4e83395 100644
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -296,7 +296,6 @@ gnucash/gtkbuilder/window-reconcile.glade
 gnucash/html/gnc-html.cpp
 gnucash/html/gnc-html-factory.cpp
 gnucash/html/gnc-html-history.cpp
-gnucash/html/gnc-html-webkit1.cpp
 gnucash/html/gnc-html-webkit2.cpp
 gnucash/html/gnc-html-webview2.cpp
 gnucash/html/html.scm

commit a6c2d024f955140ca73838b7d5831dbf0f9c517c
Author: John Ralls <jralls at ceridwen.us>
Date:   Sun Jul 19 12:19:48 2026 -0700

    Create a new HTML backend using MS WebView2 and set Win32 builds to use it.
    
    Key points:
    * The host is a GtkEventBox (not a bare GtkDrawingArea — nothing draws
      into it via Cairo) forced to own a native HWND via
      gdk_window_ensure_native(); WebView2's controller parents directly to that
      HWND and its bounds track the widget's allocation on size-allocate.
    
    * Environment/controller creation is async, but GnuCash calls show_url
      synchronously right after construction — handled with a one-slot
      pending-navigation queue, flushed once the controller is ready.
    
    * Link-click interception (NavigationStarting) distinguishes our own
      Navigate() calls from user clicks via an internal flag rather than a
      versioned IsUserInitiated property, sidestepping a QueryInterface I
      couldn't be certain would resolve correctly under MinGW.
    
    * COM completion handlers are hand-rolled (not WRL) specifically to avoid
      depending on wrl/*.h being present in the MSYS2 toolchain.
    
    * gnc-html-webview2 was adapted from gnc-html-webkit2, itself a modification of the soon to
    be deleted gnc-html-webkit1. Even though that adaptation was performed by the LLM indicated
    below the POSIX parts were lightly changed and remain essentially human output. The
    MSWindows parts, however, are generally original to the LLM to the extent that anything from
    an LLM is original. To help indicate what those parts are I have added " -- Claude Code,
    2026" attribution to the explanatory comments for those parts of the code.
    
    Assisted-by: Claude Code Sonnet 5
    Signed-off: John Ralls <jralls at ceridwen.us>

diff --git a/CMakeLists.txt b/CMakeLists.txt
index 336e435fd1..fe34cd87f4 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -244,8 +244,17 @@ pkg_check_modules (LIBXML2 REQUIRED libxml-2.0>=2.15.0)
 pkg_check_modules (LIBXSLT REQUIRED libxslt)
 if (WITH_GNUCASH)
   if (WIN32)
-    pkg_check_modules (WEBKIT REQUIRED IMPORTED_TARGET webkitgtk-3.0)
-    set(WEBKIT1 1 CACHE INTERNAL "WebKitGtk")
+    # The MSYS2 mingw-w64-ucrt-x86_64-webview2-loader package ships
+    # only WebView2Loader.dll and WebView2.h, no pkg-config file so we
+    # must check for the header file instead.
+    find_path (WEBVIEW2_INCLUDE_DIR NAMES WebView2.h)
+    if (NOT WEBVIEW2_INCLUDE_DIR)
+      message (SEND_ERROR "WebView2.h not found. Install the MSYS2 "
+        "mingw-w64-ucrt-x86_64-webview2-loader package (or set "
+        "WEBVIEW2_INCLUDE_DIR manually).")
+    endif()
+    set (WEBVIEW2_INCLUDE_DIRS ${WEBVIEW2_INCLUDE_DIR})
+    set(WEBVIEW2 1 CACHE INTERNAL "WebView2")
   else()
     # Hold at 2.32, the latest version successfully built on macOS.
     pkg_check_modules (WEBKIT IMPORTED_TARGET webkit2gtk-4.0>=2.32.0)
diff --git a/common/config.h.cmake.in b/common/config.h.cmake.in
index e1ed479509..3673ddfeda 100644
--- a/common/config.h.cmake.in
+++ b/common/config.h.cmake.in
@@ -316,4 +316,5 @@
 /* Definitions for what WEBKIT version we're compiling against: */
 #cmakedefine WEBKIT2 1
 #cmakedefine WEBKIT1 1
+#cmakedefine WEBVIEW2 1
 
diff --git a/gnucash/html/CMakeLists.txt b/gnucash/html/CMakeLists.txt
index 197bee7dec..ae53f42977 100644
--- a/gnucash/html/CMakeLists.txt
+++ b/gnucash/html/CMakeLists.txt
@@ -6,6 +6,7 @@ set (html_HEADERS
   gnc-html-factory.hpp
   gnc-html-extras.h
   gnc-html-webkit-p.hpp
+  gnc-html-webview2-p.hpp
   gnc-html-webkit.hpp
 )
 
@@ -23,14 +24,18 @@ set (html_SOURCES
   gnc-html-factory.cpp
 )
 
-if (WEBKIT1)
+if (WEBVIEW2)
+  list(APPEND html_HEADERS gnc-html-webview2.hpp)
+  list(APPEND html_SOURCES gnc-html-webview2.cpp)
+  set(html_EXTRA_DIST gnc-html-webkit1.hpp gnc-html-webkit1.cpp gnc-html-webkit2.hpp gnc-html-webkit2.cpp)
+elseif (WEBKIT1)
   list(APPEND html_HEADERS gnc-html-webkit1.hpp)
   list(APPEND html_SOURCES gnc-html-webkit1.cpp)
-  set(html_EXTRA_DIST gnc-html-webkit2.hpp gnc-html-webkit2.cpp)
+  set(html_EXTRA_DIST gnc-html-webkit2.hpp gnc-html-webkit2.cpp gnc-html-webview2.hpp gnc-html-webview2.cpp)
 else ()
   list(APPEND html_HEADERS gnc-html-webkit2.hpp)
   list(APPEND html_SOURCES gnc-html-webkit2.cpp)
-  set(html_EXTRA_DIST gnc-html-webkit1.hpp gnc-html-webkit1.cpp)
+  set(html_EXTRA_DIST gnc-html-webkit1.hpp gnc-html-webkit1.cpp gnc-html-webview2.hpp gnc-html-webview2.cpp)
 endif()
 
 
@@ -59,9 +64,17 @@ target_link_libraries(gnc-html
         gnc-engine
         gnc-gnome-utils
         PkgConfig::GTK3
-        PkgConfig::WEBKIT
         ${GUILE_LDFLAGS})
 
+if (WEBVIEW2)
+# The MSYS2 webview2-loader package doesn't include a link (.dll.a)
+# library so we have to dlopen the library at runtime. We just need to
+# include the header here.
+  target_include_directories(gnc-html PRIVATE ${WEBVIEW2_INCLUDE_DIRS})
+else()
+  target_link_libraries(gnc-html PRIVATE PkgConfig::WEBKIT)
+endif()
+
 target_compile_definitions(gnc-html PRIVATE -DG_LOG_DOMAIN=\"gnc.html\")
 
 
diff --git a/gnucash/html/gnc-html-factory.cpp b/gnucash/html/gnc-html-factory.cpp
index 8e3bd91f9f..1fe44490c3 100644
--- a/gnucash/html/gnc-html-factory.cpp
+++ b/gnucash/html/gnc-html-factory.cpp
@@ -38,7 +38,11 @@ G_GNUC_UNUSED static QofLogModule log_module = GNC_MOD_HTML;
 GncHtml*
 gnc_html_factory_create_html( void ) noexcept
 {
+#ifdef WEBVIEW2
+    return gnc_html_webview2_new();
+#else
     return gnc_html_webkit_new();
+#endif
 }
 
 gboolean
diff --git a/gnucash/html/gnc-html-webkit.hpp b/gnucash/html/gnc-html-webkit.hpp
index 20e48bf974..b567c70c00 100644
--- a/gnucash/html/gnc-html-webkit.hpp
+++ b/gnucash/html/gnc-html-webkit.hpp
@@ -21,7 +21,9 @@
 \********************************************************************/
 
 #include <config.h>
-#ifdef WEBKIT1
+#if defined(WEBVIEW2)
+#include "gnc-html-webview2.hpp"
+#elif defined(WEBKIT1)
 #include "gnc-html-webkit1.hpp"
 #else
 #include "gnc-html-webkit2.hpp"
diff --git a/gnucash/html/gnc-html-webview2-p.hpp b/gnucash/html/gnc-html-webview2-p.hpp
new file mode 100644
index 0000000000..947f6fa6d2
--- /dev/null
+++ b/gnucash/html/gnc-html-webview2-p.hpp
@@ -0,0 +1,77 @@
+/********************************************************************
+ * gnc-html-webview2-p.hpp -- display html with gnc special tags      *
+ * Copyright (C) 2026 John Ralls <jralls at ceridwen.us>                *
+ *                                                                  *
+ * This program is free software; you can redistribute it and/or    *
+ * modify it under the terms of the GNU General Public License as   *
+ * published by the Free Software Foundation; either version 2 of   *
+ * the License, or (at your option) any later version.              *
+ *                                                                  *
+ * This program is distributed in the hope that it will be useful,  *
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of   *
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the    *
+ * GNU General Public License for more details.                     *
+ *                                                                  *
+ * You should have received a copy of the GNU General Public License*
+ * along with this program; if not, contact:                        *
+ *                                                                  *
+ * Free Software Foundation           Voice:  +1-617-542-5942       *
+ * 51 Franklin Street, Fifth Floor    Fax:    +1-617-542-2652       *
+ * Boston, MA  02110-1301,  USA       gnu at gnu.org                   *
+\********************************************************************/
+
+#ifndef GNC_HTML_WEBVIEW2_P_H
+#define GNC_HTML_WEBVIEW2_P_H
+
+#include "gnc-html-p.h"
+
+#include <windows.h>
+#include <WebView2.h>
+
+enum GncWebview2PendingKind
+{
+    PENDING_NONE,
+    PENDING_URI,
+    PENDING_STRING
+};
+
+struct GncHtmlWebview2Private
+{
+    GncHtmlPrivate base;
+
+    /* Plain GTK widget forced to own a private, natively-backed HWND
+     * (see gdk_window_ensure_native()). The WebView2 controller is
+     * parented directly to that HWND and its bounds are kept in sync
+     * with the widget's allocation, so it paints itself as a child
+     * window exactly covering this widget -- not literally Cairo
+     * content, but visually indistinguishable from it. */
+    GtkWidget* socket;
+
+    ICoreWebView2Environment* environment;
+    ICoreWebView2Controller* controller;
+    ICoreWebView2* webview;
+    EventRegistrationToken nav_starting_token;
+    gboolean has_nav_starting_token;
+
+    gboolean environment_creating;
+
+    /* Set immediately before a Navigate()/NavigateToString() call that
+     * originates from our own code (loading a report, showing an error
+     * page, etc.), so the NavigationStarting handler can tell those
+     * apart from the user clicking a link inside the rendered page. */
+    gboolean navigating_internally;
+
+    gboolean disposed;
+
+    gchar* html_string; /* last html shown; used for export/print */
+
+    /* Environment/controller creation is asynchronous, but callers
+     * routinely call gnc_html_show_url()/gnc_html_show_data()
+     * synchronously right after construction (e.g. to load the initial
+     * report). Stash the most recent such request here and replay it
+     * once the controller becomes ready. */
+    GncWebview2PendingKind pending_kind;
+    gchar* pending_payload;
+};
+
+#endif
diff --git a/gnucash/html/gnc-html-webview2.cpp b/gnucash/html/gnc-html-webview2.cpp
new file mode 100644
index 0000000000..638ae79464
--- /dev/null
+++ b/gnucash/html/gnc-html-webview2.cpp
@@ -0,0 +1,1449 @@
+/********************************************************************
+ * gnc-html-webview2.cpp -- gnucash report renderer using WebView2  *
+ *                                                                  *
+ * Copyright (C) 2000 Bill Gribble <grib at billgribble.com>           *
+ * Copyright (C) 2001 Linas Vepstas <linas at linas.org>               *
+ * Copyright (C) 2009 Phil Longstaff <plongstaff at rogers.com>        *
+ * Copyright (C) 2026 John Ralls <jralls at ceridwen.us>                *
+ *                                                                  *
+ * This program is free software; you can redistribute it and/or    *
+ * modify it under the terms of the GNU General Public License as   *
+ * published by the Free Software Foundation; either version 2 of   *
+ * the License, or (at your option) any later version.              *
+ *                                                                  *
+ * This program is distributed in the hope that it will be useful,  *
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of   *
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the    *
+ * GNU General Public License for more details.                     *
+ *                                                                  *
+ * You should have received a copy of the GNU General Public License*
+ * along with this program; if not, contact:                        *
+ *                                                                  *
+ * Free Software Foundation           Voice:  +1-617-542-5942       *
+ * 51 Franklin Street, Fifth Floor    Fax:    +1-617-542-2652       *
+ * Boston, MA  02110-1301,  USA       gnu at gnu.org                   *
+ ********************************************************************/
+
+/* This backend embeds Microsoft Edge WebView2 as a native child HWND
+ * parented to a plain GTK widget ("socket") that has been forced to own
+ * its own native window via gdk_window_ensure_native(). WebView2's
+ * controller bounds are kept in sync with the socket's GTK allocation,
+ * so the browser paints itself as if it were the socket's content.
+ *
+ * WebView2 environment/controller creation is asynchronous and driven
+ * by COM callbacks dispatched through the normal Win32 message queue,
+ * which GDK's win32 backend already pumps as part of the GLib main
+ * loop -- no extra plumbing is required as long as everything is
+ * created on the GTK/UI thread, which it is here.
+ *
+ * The completion-handler classes below implement the relevant WebView2
+ * callback interfaces by hand instead of via Microsoft::WRL, to avoid
+ * depending on the wrl (Windows Runtime Library) headers being present
+ * in the MinGW toolchain.
+ * They answer QueryInterface() unconditionally with their own vtable,
+ * which is safe here because each class implements exactly one
+ * interface (besides IUnknown) and is only ever handed to WebView2
+ * APIs that call Invoke()/AddRef()/Release() directly through the
+ * typed pointer they were given -- never re-queried for another
+ * interface.
+ */
+
+#include <config.h>
+
+#include <windows.h>
+#include <WebView2.h>
+
+#include <gtk/gtk.h>
+#include <gdk/gdkwin32.h>
+#include <glib/gi18n.h>
+#include <glib/gstdio.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <cstdlib>
+#include <cstring>
+#include <cerrno>
+#include <fcntl.h>
+#include <unistd.h>
+#include <regex.h>
+#include <atomic>
+
+#include "Account.h"
+#include "gnc-prefs.h"
+#include "gnc-gui-query.h"
+#include "gnc-engine.h"
+#include "gnc-html.h"
+#include "gnc-html-webkit.hpp"
+#include "gnc-html-history.h"
+
+G_DEFINE_TYPE(GncHtmlWebview2, gnc_html_webview2, GNC_TYPE_HTML)
+
+static void gnc_html_webview2_dispose (GObject* obj);
+static void gnc_html_webview2_finalize (GObject* obj);
+
+#define GNC_HTML_WEBVIEW2_GET_PRIVATE(o) (GNC_HTML_WEBVIEW2(o)->priv)
+
+#include "gnc-html-webview2-p.hpp"
+
+/* indicates the debugging module that this .o belongs to.  */
+static QofLogModule log_module = GNC_MOD_HTML;
+
+/* hashes an HTML <object classid="ID"> classid to a handler function */
+extern GHashTable* gnc_html_object_handlers;
+
+/* hashes handlers for loading different URLType data */
+extern GHashTable* gnc_html_stream_handlers;
+
+/* hashes handlers for handling different URLType data */
+extern GHashTable* gnc_html_url_handlers;
+
+static char error_404_format[] = "<html><body><h3>%s</h3><p>%s</body></html>";
+static char error_404_title[] = N_("Not found");
+static char error_404_body[] = N_("The specified URL could not be loaded.");
+
+#define GNC_PREF_RPT_DFLT_ZOOM "default-zoom"
+
+static void show_url (GncHtml* self, URLType type,
+                                    const gchar* location, const gchar* label,
+                                    gboolean new_window_hint);
+static void show_data (GncHtml* self, const gchar* data, int datalen);
+static void reload (GncHtml* self, gboolean force_rebuild);
+static void copy_to_clipboard (GncHtml* self);
+static gboolean export_to_file (GncHtml* self, const gchar* filepath);
+static void print (GncHtml* self, const gchar* jobname);
+static void cancel (GncHtml* self);
+static void set_parent (GncHtml* self, GtkWindow* parent);
+static void default_zoom_changed(gpointer prefs, gchar *pref, gpointer user_data);
+static void navigate_uri (GncHtmlWebview2* self, const gchar* uri);
+static void navigate_string (GncHtmlWebview2* self, const gchar* html);
+static gboolean load_to_stream (GncHtmlWebview2* self, URLType type,
+                                const gchar* location, const gchar* label);
+
+// Minimal hand-rolled COM completion/event-handler objects.
+
+template <typename Interface>
+class GncWebView2Sink : public Interface
+{
+public:
+    explicit GncWebView2Sink (GncHtmlWebview2* html)
+        : m_html (GNC_HTML_WEBVIEW2 (g_object_ref (G_OBJECT (html)))) {}
+
+    /* Release() below deletes through this base pointer regardless of
+     * which concrete handler subclass it is. -- Claude Code, 2026  */
+    virtual ~GncWebView2Sink () = default;
+
+    HRESULT STDMETHODCALLTYPE QueryInterface (REFIID, void** ppv) override
+    {
+        if (!ppv)
+            return E_POINTER;
+        *ppv = static_cast<Interface*> (this);
+        AddRef ();
+        return S_OK;
+    }
+
+    ULONG STDMETHODCALLTYPE AddRef () override
+    {
+        return ++m_refs;
+    }
+
+    ULONG STDMETHODCALLTYPE Release () override
+    {
+        auto refs = --m_refs;
+        if (refs == 0)
+        {
+            g_object_unref (m_html);
+            delete this;
+        }
+        return refs;
+    }
+
+protected:
+    GncHtmlWebview2* m_html;
+
+private:
+    std::atomic<ULONG> m_refs {1};
+};
+
+class GncWebView2EnvironmentHandler final
+    : public GncWebView2Sink<ICoreWebView2CreateCoreWebView2EnvironmentCompletedHandler>
+{
+public:
+    using GncWebView2Sink::GncWebView2Sink;
+
+    HRESULT STDMETHODCALLTYPE Invoke (HRESULT result, ICoreWebView2Environment* env) override;
+};
+
+class GncWebView2ControllerHandler final
+    : public GncWebView2Sink<ICoreWebView2CreateCoreWebView2ControllerCompletedHandler>
+{
+public:
+    using GncWebView2Sink::GncWebView2Sink;
+
+    HRESULT STDMETHODCALLTYPE Invoke (HRESULT result, ICoreWebView2Controller* controller) override;
+};
+
+class GncWebView2NavStartingHandler final
+    : public GncWebView2Sink<ICoreWebView2NavigationStartingEventHandler>
+{
+public:
+    using GncWebView2Sink::GncWebView2Sink;
+
+    HRESULT STDMETHODCALLTYPE Invoke (ICoreWebView2* sender,
+                                      ICoreWebView2NavigationStartingEventArgs* args) override;
+};
+
+/* Minimal hand-rolled implementation of
+ * ICoreWebView2EnvironmentOptions (verified against WebView2.h
+ * provided by MSYS2 mingw-w64-webview2-loader package version
+ * 1.0.3912.50-1: get/put pairs for AdditionalBrowserArguments,
+ * Language, TargetCompatibleBrowserVersion,
+ * AllowSingleSignOnUsingOSPrimaryAccount -- nothing else). Used
+ * instead of Microsoft's own WebView2EnvironmentOptions.h
+ * implementation class, which the MSYS2 webview2-loader package
+ * doesn't ship, and instead of the
+ * WEBVIEW2_ADDITIONAL_BROWSER_ARGUMENTS environment-variable
+ * fallback, which turned out not to be honored by this loader/runtime
+ * combination. Only AdditionalBrowserArguments is actually exercised;
+ * the rest are implemented for interface completeness.
+ *  -- Claude Code, 2026 */
+class GncWebView2EnvironmentOptions final : public ICoreWebView2EnvironmentOptions
+{
+public:
+    GncWebView2EnvironmentOptions () = default;
+
+    HRESULT STDMETHODCALLTYPE QueryInterface (REFIID, void** ppv) override
+    {
+        if (!ppv)
+            return E_POINTER;
+        *ppv = static_cast<ICoreWebView2EnvironmentOptions*> (this);
+        AddRef ();
+        return S_OK;
+    }
+
+    ULONG STDMETHODCALLTYPE AddRef () override
+    {
+        return ++m_refs;
+    }
+
+    ULONG STDMETHODCALLTYPE Release () override
+    {
+        auto refs = --m_refs;
+        if (refs == 0)
+            delete this;
+        return refs;
+    }
+
+    HRESULT STDMETHODCALLTYPE get_AdditionalBrowserArguments (LPWSTR* value) override
+    {
+        return dup_out (m_additional_browser_arguments, value);
+    }
+    HRESULT STDMETHODCALLTYPE put_AdditionalBrowserArguments (LPCWSTR value) override
+    {
+        return set_in (&m_additional_browser_arguments, value);
+    }
+    HRESULT STDMETHODCALLTYPE get_Language (LPWSTR* value) override
+    {
+        return dup_out (m_language, value);
+    }
+    HRESULT STDMETHODCALLTYPE put_Language (LPCWSTR value) override
+    {
+        return set_in (&m_language, value);
+    }
+    HRESULT STDMETHODCALLTYPE get_TargetCompatibleBrowserVersion (LPWSTR* value) override
+    {
+        return dup_out (m_target_compatible_browser_version, value);
+    }
+    HRESULT STDMETHODCALLTYPE put_TargetCompatibleBrowserVersion (LPCWSTR value) override
+    {
+        return set_in (&m_target_compatible_browser_version, value);
+    }
+    HRESULT STDMETHODCALLTYPE get_AllowSingleSignOnUsingOSPrimaryAccount (BOOL* allow) override
+    {
+        if (!allow)
+            return E_POINTER;
+        *allow = m_allow_sso;
+        return S_OK;
+    }
+    HRESULT STDMETHODCALLTYPE put_AllowSingleSignOnUsingOSPrimaryAccount (BOOL allow) override
+    {
+        m_allow_sso = allow;
+        return S_OK;
+    }
+
+private:
+    ~GncWebView2EnvironmentOptions ()
+    {
+        g_free (m_additional_browser_arguments);
+        g_free (m_language);
+        g_free (m_target_compatible_browser_version);
+    }
+
+    /* value is already UTF-16 (LPCWSTR); gunichar2 and wchar_t are both
+     * 16 bits wide on Windows, so this is a plain length+copy, not a
+     * UTF-8/UTF-16 conversion.  -- Claude Code, 2026 */
+    static HRESULT set_in (gunichar2** dest, LPCWSTR value)
+    {
+        g_free (*dest);
+        *dest = nullptr;
+        if (value)
+        {
+            size_t len = 0;
+            while (value[len])
+                ++len;
+            auto buf = static_cast<gunichar2*> (g_malloc ((len + 1) * sizeof (gunichar2)));
+            memcpy (buf, value, len * sizeof (gunichar2));
+            buf[len] = 0;
+            *dest = buf;
+        }
+        return S_OK;
+    }
+
+    /* Callers of get_* own the returned string and must CoTaskMemFree()
+     * it, per COM convention. -- Claude Code, 2026 */
+    static HRESULT dup_out (const gunichar2* src, LPWSTR* out)
+    {
+        if (!out)
+            return E_POINTER;
+        size_t len = 0;
+        if (src)
+            while (src[len])
+                ++len;
+        auto buf = static_cast<gunichar2*> (CoTaskMemAlloc ((len + 1) * sizeof (gunichar2)));
+        if (!buf)
+            return E_OUTOFMEMORY;
+        if (src)
+            memcpy (buf, src, len * sizeof (gunichar2));
+        buf[len] = 0;
+        *out = reinterpret_cast<LPWSTR> (buf);
+        return S_OK;
+    }
+
+    std::atomic<ULONG> m_refs {1};
+    gunichar2* m_additional_browser_arguments = nullptr;
+    gunichar2* m_language = nullptr;
+    gunichar2* m_target_compatible_browser_version = nullptr;
+    BOOL m_allow_sso = FALSE;
+};
+
+// *****************************************************************************
+
+static void
+ensure_com_initialized ()
+{
+    static bool done = false;
+    if (done)
+        return;
+    done = true;
+
+    HRESULT hr = CoInitializeEx (nullptr, COINIT_APARTMENTTHREADED);
+    if (FAILED (hr) && hr != RPC_E_CHANGED_MODE)
+        PWARN ("CoInitializeEx failed: 0x%08lx", (unsigned long) hr);
+    else if (hr == RPC_E_CHANGED_MODE)
+        PWARN ("COM was already initialized as multi-threaded; "
+               "WebView2 requires a single-threaded apartment on the UI thread.");
+}
+
+/* The MSYS2 webview2-loader package ships WebView2Loader.dll and
+ * WebView2.h but no import library, so CreateCoreWebView2EnvironmentWithOptions
+ * -- the loader DLL's only entry point we need -- is resolved at
+ * runtime instead of being linked directly. This also means a missing
+ * or incompatible WebView2Loader.dll fails gracefully at first use
+ * rather than preventing the executable from starting at all.
+ * -- Claude Code, 2026 */
+using CreateCoreWebView2EnvironmentWithOptionsFn = HRESULT (STDAPICALLTYPE*)(
+    PCWSTR browserExecutableFolder,
+    PCWSTR userDataFolder,
+    ICoreWebView2EnvironmentOptions* environmentOptions,
+    ICoreWebView2CreateCoreWebView2EnvironmentCompletedHandler* environmentCreatedHandler);
+
+static CreateCoreWebView2EnvironmentWithOptionsFn
+get_create_environment_fn ()
+{
+    static CreateCoreWebView2EnvironmentWithOptionsFn fn = nullptr;
+    static bool tried = false;
+
+    if (!tried)
+    {
+        tried = true;
+        HMODULE mod = LoadLibraryW (L"WebView2Loader.dll");
+        if (mod)
+            fn = reinterpret_cast<CreateCoreWebView2EnvironmentWithOptionsFn> (
+                GetProcAddress (mod, "CreateCoreWebView2EnvironmentWithOptions"));
+        if (!fn)
+            PERR ("Could not resolve CreateCoreWebView2EnvironmentWithOptions "
+                  "from WebView2Loader.dll (module %s)", mod ? "loaded" : "not found");
+    }
+    return fn;
+}
+
+static void
+flush_pending (GncHtmlWebview2* self)
+{
+    auto priv = GNC_HTML_WEBVIEW2_GET_PRIVATE (self);
+    auto kind = priv->pending_kind;
+    gchar* payload = priv->pending_payload;
+
+    priv->pending_kind = PENDING_NONE;
+    priv->pending_payload = nullptr;
+
+    if (kind == PENDING_URI)
+        navigate_uri (self, payload);
+    else if (kind == PENDING_STRING)
+        navigate_string (self, payload);
+
+    g_free (payload);
+}
+
+static void
+environment_created (GncHtmlWebview2* self, HRESULT result,
+                                   ICoreWebView2Environment* env)
+{
+    auto priv = GNC_HTML_WEBVIEW2_GET_PRIVATE (self);
+    priv->environment_creating = FALSE;
+
+    if (priv->disposed)
+        return;
+
+    if (FAILED (result) || !env)
+    {
+        PERR ("WebView2 environment creation failed: 0x%08lx", (unsigned long) result);
+        return;
+    }
+
+    priv->environment = env;
+    env->AddRef ();
+
+    if (!priv->socket || !gtk_widget_get_window (priv->socket))
+    {
+        PERR ("WebView2 socket widget was destroyed before the environment was ready");
+        return;
+    }
+
+    HWND hwnd = static_cast<HWND> (GDK_WINDOW_HWND (gtk_widget_get_window (priv->socket)));
+    auto handler = new GncWebView2ControllerHandler (self);
+    HRESULT hr = env->CreateCoreWebView2Controller (hwnd, handler);
+    if (FAILED (hr))
+    {
+        PERR ("CreateCoreWebView2Controller failed: 0x%08lx", (unsigned long) hr);
+        handler->Release ();
+    }
+}
+
+HRESULT STDMETHODCALLTYPE
+GncWebView2EnvironmentHandler::Invoke (HRESULT result, ICoreWebView2Environment* env)
+{
+    environment_created (m_html, result, env);
+    return S_OK;
+}
+
+static void
+controller_created (GncHtmlWebview2* self, HRESULT result,
+                                  ICoreWebView2Controller* controller)
+{
+    auto priv = GNC_HTML_WEBVIEW2_GET_PRIVATE (self);
+
+    if (priv->disposed)
+    {
+        if (controller)
+            controller->Close ();
+        return;
+    }
+
+    if (FAILED (result) || !controller)
+    {
+        PERR ("WebView2 controller creation failed: 0x%08lx", (unsigned long) result);
+        return;
+    }
+
+    priv->controller = controller;
+    controller->AddRef ();
+    controller->get_CoreWebView2 (&priv->webview);
+
+    GtkAllocation alloc;
+    gtk_widget_get_allocation (priv->socket, &alloc);
+    RECT bounds { 0, 0, alloc.width, alloc.height };
+    priv->controller->put_Bounds (bounds);
+    priv->controller->put_IsVisible (gtk_widget_get_mapped (priv->socket));
+
+    gdouble zoom = gnc_prefs_get_float (GNC_PREFS_GROUP_GENERAL_REPORT, GNC_PREF_RPT_DFLT_ZOOM);
+    priv->controller->put_ZoomFactor (zoom);
+
+    if (priv->webview)
+    {
+        auto nav_handler = new GncWebView2NavStartingHandler (self);
+        priv->webview->add_NavigationStarting (nav_handler, &priv->nav_starting_token);
+        priv->has_nav_starting_token = TRUE;
+        nav_handler->Release ();
+    }
+
+    flush_pending (self);
+}
+
+HRESULT STDMETHODCALLTYPE
+GncWebView2ControllerHandler::Invoke (HRESULT result, ICoreWebView2Controller* controller)
+{
+    controller_created (m_html, result, controller);
+    return S_OK;
+}
+
+static void
+navigation_starting (GncHtmlWebview2* self,
+                                   ICoreWebView2NavigationStartingEventArgs* args)
+{
+    auto priv = GNC_HTML_WEBVIEW2_GET_PRIVATE (self);
+
+    /* Navigations we kicked off ourselves (loading a report, showing an
+     * error page) must not be re-routed through gnc_html_show_url(). Only
+     * navigations we didn't initiate -- in practice, the user clicking a
+     * link inside the rendered report -- should be intercepted.
+     *  -- Claude Code, 2026 */
+    if (priv->navigating_internally)
+    {
+        priv->navigating_internally = FALSE;
+        return;
+    }
+
+    LPWSTR wuri = nullptr;
+    if (FAILED (args->get_Uri (&wuri)) || !wuri)
+        return;
+
+    gchar* uri = g_utf16_to_utf8 (reinterpret_cast<const gunichar2*> (wuri), -1,
+                                  nullptr, nullptr, nullptr);
+    CoTaskMemFree (wuri);
+    if (!uri)
+        return;
+
+    gchar* location = nullptr;
+    gchar* label = nullptr;
+    URLType scheme = gnc_html_parse_url (GNC_HTML (self), uri, &location, &label);
+
+    if (g_strcmp0 (scheme, URL_TYPE_FILE) != 0)
+    {
+        args->put_Cancel (TRUE);
+        show_url (GNC_HTML (self), scheme, location, label, FALSE);
+    }
+
+    g_free (location);
+    g_free (label);
+    g_free (uri);
+}
+
+HRESULT STDMETHODCALLTYPE
+GncWebView2NavStartingHandler::Invoke (ICoreWebView2* /*sender*/,
+                                       ICoreWebView2NavigationStartingEventArgs* args)
+{
+    navigation_starting (m_html, args);
+    return S_OK;
+}
+
+// *****************************************************************************
+
+static void
+navigate_uri (GncHtmlWebview2* self, const gchar* uri)
+{
+    auto priv = GNC_HTML_WEBVIEW2_GET_PRIVATE (self);
+
+    if (!priv->webview)
+    {
+        g_free (priv->pending_payload);
+        priv->pending_payload = g_strdup (uri);
+        priv->pending_kind = PENDING_URI;
+        return;
+    }
+
+    gunichar2* wuri = g_utf8_to_utf16 (uri, -1, nullptr, nullptr, nullptr);
+    priv->navigating_internally = TRUE;
+    priv->webview->Navigate (reinterpret_cast<LPCWSTR> (wuri));
+    g_free (wuri);
+}
+
+static void
+navigate_string (GncHtmlWebview2* self, const gchar* html)
+{
+    auto priv = GNC_HTML_WEBVIEW2_GET_PRIVATE (self);
+
+    if (!priv->webview)
+    {
+        g_free (priv->pending_payload);
+        priv->pending_payload = g_strdup (html);
+        priv->pending_kind = PENDING_STRING;
+        return;
+    }
+
+    gunichar2* whtml = g_utf8_to_utf16 (html, -1, nullptr, nullptr, nullptr);
+    priv->navigating_internally = TRUE;
+    priv->webview->NavigateToString (reinterpret_cast<LPCWSTR> (whtml));
+    g_free (whtml);
+}
+
+// *****************************************************************************
+
+static void
+socket_realize_cb (GtkWidget* socket, gpointer user_data)
+{
+    auto self = GNC_HTML_WEBVIEW2 (user_data);
+    auto priv = GNC_HTML_WEBVIEW2_GET_PRIVATE (self);
+
+    if (priv->environment || priv->environment_creating)
+        return;
+
+    auto create_env_fn = get_create_environment_fn ();
+    if (!create_env_fn)
+    {
+        gnc_error_dialog (GTK_WINDOW (priv->base.parent), "%s",
+                          _("The Microsoft Edge WebView2 Runtime is required to display "
+                            "reports, but its loader library (WebView2Loader.dll) could "
+                            "not be found or is invalid."));
+        return;
+    }
+
+    gdk_window_ensure_native (gtk_widget_get_window (socket));
+
+    ensure_com_initialized ();
+    priv->environment_creating = TRUE;
+
+    gchar* user_data_dir = g_build_filename (g_get_user_data_dir (), "gnucash",
+                                             "webview2", (gchar*)nullptr);
+    gunichar2* wuser_data_dir = g_utf8_to_utf16 (user_data_dir, -1, nullptr, nullptr, nullptr);
+    g_free (user_data_dir);
+    /* A non-null ICoreWebView2EnvironmentOptions here reliably crashes
+     * inside WebView2Loader.dll itself (verified with gdb: it reads
+     * back our AdditionalBrowserArguments string correctly, then faults
+     * writing through a null destination pointer while copying it into
+     * its own internal buffer) with the MSYS2 mingw-w64-ucrt-x86_64-webview2-loader
+     * package (1.0.3912.50) -- looks like a real bug in that build, not
+     * something fixable from the caller side. Passing nullptr avoids it.
+     *  -- Claude Code, 2026 */
+    auto handler = new GncWebView2EnvironmentHandler (self);
+    HRESULT hr = create_env_fn (
+        nullptr, reinterpret_cast<LPCWSTR> (wuser_data_dir), nullptr, handler);
+    g_free (wuser_data_dir);
+
+    if (FAILED (hr))
+    {
+        PERR ("CreateCoreWebView2EnvironmentWithOptions failed: 0x%08lx", (unsigned long) hr);
+        handler->Release ();
+        priv->environment_creating = FALSE;
+    }
+}
+
+static void
+socket_size_allocate_cb (GtkWidget* socket, GtkAllocation* allocation, gpointer user_data)
+{
+    auto priv = GNC_HTML_WEBVIEW2_GET_PRIVATE (GNC_HTML_WEBVIEW2 (user_data));
+    if (priv->controller)
+    {
+        RECT bounds { 0, 0, allocation->width, allocation->height };
+        priv->controller->put_Bounds (bounds);
+    }
+}
+
+static void
+socket_map_cb (GtkWidget* socket, gpointer user_data)
+{
+    auto priv = GNC_HTML_WEBVIEW2_GET_PRIVATE (GNC_HTML_WEBVIEW2 (user_data));
+    if (priv->controller)
+        priv->controller->put_IsVisible (TRUE);
+}
+
+static void
+socket_unmap_cb (GtkWidget* socket, gpointer user_data)
+{
+    auto priv = GNC_HTML_WEBVIEW2_GET_PRIVATE (GNC_HTML_WEBVIEW2 (user_data));
+    if (priv->controller)
+        priv->controller->put_IsVisible (FALSE);
+}
+
+static void
+gnc_html_webview2_init (GncHtmlWebview2* self)
+{
+     const gpointer p = g_realloc (GNC_HTML(self)->priv, sizeof(GncHtmlWebview2Private));
+     auto new_priv = reinterpret_cast<GncHtmlWebview2Private *>(p);
+     auto priv = self->priv = new_priv;
+     GNC_HTML(self)->priv = (GncHtmlPrivate*)priv;
+
+     priv->html_string = nullptr;
+     priv->environment = nullptr;
+     priv->controller = nullptr;
+     priv->webview = nullptr;
+     priv->nav_starting_token = EventRegistrationToken {};
+     priv->has_nav_starting_token = FALSE;
+     priv->environment_creating = FALSE;
+     priv->navigating_internally = FALSE;
+     priv->disposed = FALSE;
+     priv->pending_kind = PENDING_NONE;
+     priv->pending_payload = nullptr;
+
+     /* A plain widget that owns its own native HWND once realized -- see
+      * webview2_socket_realize_cb() -- which becomes the WebView2
+      * controller's parent window. GtkEventBox is used rather than
+      * GtkDrawingArea because nothing is drawn into it via Cairo; its
+      * only job is to provide a native window for WebView2 to paint
+      * into as a child HWND. */
+     priv->socket = gtk_event_box_new ();
+     gtk_widget_set_can_focus (priv->socket, TRUE);
+
+     gtk_container_add (GTK_CONTAINER(priv->base.container),
+                        priv->socket);
+
+     g_object_ref_sink (priv->base.container);
+
+     g_signal_connect (priv->socket, "realize",
+                       G_CALLBACK (socket_realize_cb), self);
+     g_signal_connect (priv->socket, "size-allocate",
+                       G_CALLBACK (socket_size_allocate_cb), self);
+     g_signal_connect (priv->socket, "map",
+                       G_CALLBACK (socket_map_cb), self);
+     g_signal_connect (priv->socket, "unmap",
+                       G_CALLBACK (socket_unmap_cb), self);
+
+     gnc_prefs_register_cb (GNC_PREFS_GROUP_GENERAL_REPORT,
+                            GNC_PREF_RPT_DFLT_ZOOM,
+                            reinterpret_cast<gpointer>(default_zoom_changed),
+                            self);
+
+     LEAVE("retval %p", self);
+}
+
+static void
+gnc_html_webview2_class_init (GncHtmlWebview2Class* klass)
+{
+     GObjectClass* gobject_class = G_OBJECT_CLASS(klass);
+     GncHtmlClass* html_class = GNC_HTML_CLASS(klass);
+
+     gobject_class->dispose = gnc_html_webview2_dispose;
+     gobject_class->finalize = gnc_html_webview2_finalize;
+
+     html_class->show_url = show_url;
+     html_class->show_data = show_data;
+     html_class->reload = reload;
+     html_class->copy_to_clipboard = copy_to_clipboard;
+     html_class->export_to_file = export_to_file;
+     html_class->print = print;
+     html_class->cancel = cancel;
+     html_class->set_parent = set_parent;
+}
+
+static void
+gnc_html_webview2_dispose (GObject* obj)
+{
+     GncHtmlWebview2* self = GNC_HTML_WEBVIEW2(obj);
+     GncHtmlWebview2Private* priv = GNC_HTML_WEBVIEW2_GET_PRIVATE(self);
+
+     priv->disposed = TRUE;
+
+     if (priv->webview != nullptr && priv->has_nav_starting_token)
+     {
+          priv->webview->remove_NavigationStarting (priv->nav_starting_token);
+          priv->has_nav_starting_token = FALSE;
+     }
+
+     if (priv->controller != nullptr)
+     {
+          priv->controller->Close();
+          priv->controller->Release();
+          priv->controller = nullptr;
+     }
+
+     if (priv->webview != nullptr)
+     {
+          priv->webview->Release();
+          priv->webview = nullptr;
+     }
+
+     if (priv->environment != nullptr)
+     {
+          priv->environment->Release();
+          priv->environment = nullptr;
+     }
+
+     if (priv->socket != nullptr)
+     {
+          gtk_container_remove (GTK_CONTAINER(priv->base.container),
+                                priv->socket);
+
+          priv->socket = nullptr;
+     }
+
+     g_clear_pointer (&priv->html_string, g_free);
+     g_clear_pointer (&priv->pending_payload, g_free);
+
+     gnc_prefs_remove_cb_by_func (GNC_PREFS_GROUP_GENERAL_REPORT,
+                                  GNC_PREF_RPT_DFLT_ZOOM,
+                                  reinterpret_cast<gpointer>(default_zoom_changed),
+                                  obj);
+
+     G_OBJECT_CLASS(gnc_html_webview2_parent_class)->dispose (obj);
+}
+
+static void
+gnc_html_webview2_finalize (GObject* obj)
+{
+     GncHtmlWebview2* self = GNC_HTML_WEBVIEW2(obj);
+
+     self->priv = nullptr;
+
+     G_OBJECT_CLASS(gnc_html_webview2_parent_class)->finalize (obj);
+}
+
+// *****************************************************************************
+
+static char*
+extract_base_name(URLType type, const gchar* path)
+{
+     constexpr gchar       machine_rexp[] = "^(//[^/]*)/*(/.*)?$";
+     constexpr gchar       path_rexp[] = "^/*(.*)/+([^/]*)$";
+     regex_t     compiled_m, compiled_p;
+     constexpr size_t MATCH_LEN = 4;
+     regmatch_t  match[MATCH_LEN];
+     gchar       * machine = nullptr, * location = nullptr, * base = nullptr;
+     gchar       * basename = nullptr;
+
+     DEBUG(" ");
+     if (!path) return nullptr;
+
+     regcomp(&compiled_m, machine_rexp, REG_EXTENDED);
+     regcomp(&compiled_p, path_rexp, REG_EXTENDED);
+
+     if (!g_strcmp0 (type, URL_TYPE_HTTP) ||
+         !g_strcmp0 (type, URL_TYPE_SECURE) ||
+         !g_strcmp0 (type, URL_TYPE_FTP))
+     {
+
+          /* step 1: split the machine name away from the path
+           * components */
+          if (!regexec(&compiled_m, path, MATCH_LEN, match, 0))
+          {
+               /* $1 is the machine name */
+               if (match[1].rm_so != -1)
+               {
+                    machine = g_strndup(path + match[1].rm_so,
+                                        match[1].rm_eo - match[1].rm_so);
+               }
+               /* $2 is the path */
+               if (match[2].rm_so != -1)
+               {
+                    location = g_strndup(path + match[2].rm_so,
+                                         match[2].rm_eo - match[2].rm_so);
+               }
+          }
+     }
+     else
+     {
+          location = g_strdup(path);
+     }
+     /* step 2: split up the path into prefix and file components */
+     if (location)
+     {
+          if (!regexec(&compiled_p, location, 4, match, 0))
+          {
+               if (match[1].rm_so != -1)
+               {
+                    base = g_strndup(location + match[1].rm_so,
+                                     match[1].rm_eo - match[1].rm_so);
+               }
+          }
+     }
+
+     regfree(&compiled_m);
+     regfree(&compiled_p);
+
+     if (machine)
+     {
+          if (base && (strlen(base) > 0))
+          {
+               basename = g_strconcat(machine, "/", base, "/", nullptr);
+          }
+          else
+          {
+               basename = g_strconcat(machine, "/", nullptr);
+          }
+     }
+     else
+     {
+          if (base && (strlen(base) > 0))
+          {
+               basename = g_strdup(base);
+          }
+     }
+
+     g_free(machine);
+     g_free(base);
+     g_free(location);
+     return basename;
+}
+
+static gboolean
+http_allowed()
+{
+     return TRUE;
+}
+
+static gboolean
+https_allowed()
+{
+     return TRUE;
+}
+
+static gchar*
+handle_embedded_object (GncHtmlWebview2* self, gchar* html_str)
+{
+     // Find the <object> tag and get the classid from it.  This will provide the correct
+     // object callback handler.  Pass the <object> entity text to the handler.  What should
+     // come back is embedded image information.
+     gchar* remainder_str = html_str;
+     gchar* object_tag;
+     gchar* end_object_tag;
+     gchar* object_contents;
+     gchar* html_str_start = nullptr;
+     gchar* html_str_middle;
+     gchar* html_str_result = nullptr;
+     gchar* classid_start;
+     gchar* classid_end;
+     gchar* classid_str;
+     gchar* new_chunk;
+     GncHTMLObjectCB h;
+
+     object_tag = g_strstr_len (remainder_str, -1, "<object classid=" );
+     while (object_tag)
+     {
+
+          classid_start = object_tag + strlen ("<object classid=" ) + 1;
+          classid_end = g_strstr_len (classid_start, -1, "\"" );
+          classid_str = g_strndup (classid_start, (classid_end - classid_start));
+
+          end_object_tag = g_strstr_len (object_tag, -1, "</object>" );
+          if (end_object_tag == nullptr)
+          {
+               /*  Hmmm... no object end tag
+                   Return the original html string because we can't properly parse it */
+               g_free (classid_str);
+               g_free (html_str_result);
+               return g_strdup (html_str);
+          }
+          end_object_tag += strlen ("</object>" );
+          object_contents = g_strndup (object_tag, (end_object_tag - object_tag));
+
+          const gpointer p = g_hash_table_lookup (gnc_html_object_handlers, classid_str);
+          h = reinterpret_cast<GncHTMLObjectCB>(p);
+          if (h != nullptr)
+          {
+               (void)h (GNC_HTML(self), object_contents, &html_str_middle);
+          }
+          else
+          {
+               html_str_middle = g_strdup_printf ("No handler found for classid \"%s\"", classid_str);
+          }
+
+          html_str_start = html_str_result;
+          new_chunk = g_strndup (remainder_str, (object_tag - remainder_str));
+          if (!html_str_start)
+               html_str_result = g_strconcat (new_chunk, html_str_middle, nullptr);
+          else
+               html_str_result = g_strconcat (html_str_start, new_chunk, html_str_middle, nullptr);
+
+          g_free (html_str_start);
+          g_free (new_chunk);
+          g_free (html_str_middle);
+
+          remainder_str = end_object_tag;
+          object_tag = g_strstr_len (remainder_str, -1, "<object classid=" );
+     }
+
+     if (html_str_result)
+     {
+          html_str_start =  html_str_result;
+          html_str_result = g_strconcat (html_str_start, remainder_str, nullptr);
+          g_free (html_str_start);
+     }
+     else
+          html_str_result = g_strdup (remainder_str);
+
+     return html_str_result;
+}
+
+static gboolean
+load_to_stream (GncHtmlWebview2* self, URLType type,
+                const gchar* location, const gchar* label)
+{
+     gchar* fdata = nullptr;
+     int fdata_len = 0;
+     GncHtmlWebview2Private* priv = GNC_HTML_WEBVIEW2_GET_PRIVATE(self);
+
+     DEBUG ("type %s, location %s, label %s", type ? type : "(null)",
+            location ? location : "(null)", label ? label : "(null)");
+
+     g_return_val_if_fail (self != nullptr, FALSE);
+
+     if (gnc_html_stream_handlers != nullptr)
+     {
+          const gpointer p = g_hash_table_lookup (gnc_html_stream_handlers, type);
+          GncHTMLStreamCB stream_handler = reinterpret_cast<GncHTMLStreamCB>(p);
+          if (stream_handler)
+          {
+              GncHtml *weak_html = GNC_HTML(self);
+
+              g_object_add_weak_pointer(G_OBJECT(self),
+                                        (gpointer*)(&weak_html));
+              bool ok = stream_handler (location, &fdata, &fdata_len);
+
+              if (!weak_html) // will be nullptr if self has been destroyed
+              {
+                  g_free (fdata);
+                  return FALSE;
+              }
+              else
+              {
+                  g_object_remove_weak_pointer(G_OBJECT(self),
+                                               (gpointer*)(&weak_html));
+              }
+
+               if (ok)
+               {
+                    fdata = fdata ? fdata : g_strdup ("" );
+
+                    // Look for "<object classid=" indicating the
+                    // beginning of an embedded graph.  If found,
+                    // handle it
+                    if (g_strstr_len (fdata, -1, "<object classid=" ) != nullptr)
+                    {
+                         gchar *new_fdata = handle_embedded_object (self, fdata);
+                         g_free (fdata);
+                         fdata = new_fdata;
+                    }
+
+                    // Save a copy for export purposes
+                    if (priv->html_string != nullptr)
+                    {
+                         g_free (priv->html_string);
+                    }
+                    priv->html_string = g_strdup (fdata);
+                    show_data (GNC_HTML(self), fdata, strlen(fdata));
+               }
+               else
+               {
+                    fdata = fdata ? fdata :
+                         g_strdup_printf (error_404_format,
+                                          _(error_404_title), _(error_404_body));
+                    navigate_string (self, fdata);
+               }
+
+               g_free (fdata);
+
+               if (label)
+               {
+                    while (gtk_events_pending())
+                    {
+                         gtk_main_iteration();
+                    }
+                    /* No action required: WebView2 jumps to the anchor on its own. */
+               }
+               return TRUE;
+          }
+     }
+
+     do
+     {
+          if (!g_strcmp0 (type, URL_TYPE_SECURE) ||
+               !g_strcmp0 (type, URL_TYPE_HTTP))
+          {
+
+               if (!g_strcmp0 (type, URL_TYPE_SECURE))
+               {
+                    if (!https_allowed())
+                    {
+                        gnc_error_dialog (GTK_WINDOW (priv->base.parent), "%s",
+                                           _("Secure HTTP access is disabled. "
+                                             "You can enable it in the Network section of "
+                                             "the Preferences dialog."));
+                         break;
+                    }
+               }
+
+               if (!http_allowed())
+               {
+                   gnc_error_dialog (GTK_WINDOW (priv->base.parent), "%s",
+                                      _("Network HTTP access is disabled. "
+                                        "You can enable it in the Network section of "
+                                        "the Preferences dialog."));
+               }
+               else
+               {
+                    gnc_build_url (type, location, label);
+               }
+          }
+          else
+          {
+               PWARN ("load_to_stream for inappropriate type\n"
+                      "\turl = '%s#%s'\n",
+                      location ? location : "(null)",
+                      label ? label : "(null)" );
+               fdata = g_strdup_printf (error_404_format,
+                                        _(error_404_title), _(error_404_body));
+               navigate_string (self, fdata);
+               g_free (fdata);
+          }
+     }
+     while (false);
+     return TRUE;
+}
+
+static void
+show_data (GncHtml* self, const gchar* data, int datalen)
+{
+     constexpr char TEMPLATE_REPORT_FILE_NAME[] = "gnc-report-XXXXXX.html";
+     g_return_if_fail (self != nullptr);
+     g_return_if_fail (GNC_IS_HTML_WEBVIEW2(self));
+
+     ENTER ("datalen %d, data %20.20s", datalen, data);
+
+     /* Export the HTML to a file and load the file URI, exactly as the
+      * webkit2 backend does: this avoids WebView2 refusing to load
+      * embedded local-file images/CSS referenced by relative path from
+      * a NavigateToString() call. */
+     gchar *filename = g_build_filename(g_get_tmp_dir(), TEMPLATE_REPORT_FILE_NAME, (gchar *)nullptr);
+     int fd = g_mkstemp (filename);
+     export_to_file (self, filename);
+     close (fd);
+     gchar *uri = g_strdup_printf ("file:///%s", filename);
+     g_free(filename);
+     DEBUG("Loading uri '%s'", uri);
+     navigate_uri (GNC_HTML_WEBVIEW2(self), uri);
+     g_free (uri);
+
+     LEAVE("");
+}
+
+static void
+show_url (GncHtml* self, URLType type,
+                        const gchar* location, const gchar* label,
+                        gboolean new_window_hint)
+{
+     GncHTMLUrlCB url_handler = nullptr;
+     bool new_window = false;
+     bool stream_loaded = false;
+
+     g_return_if_fail (self != nullptr);
+     g_return_if_fail (GNC_IS_HTML_WEBVIEW2(self));
+     g_return_if_fail (location != nullptr);
+
+     auto priv = GNC_HTML_WEBVIEW2_GET_PRIVATE(self);
+
+     /* make sure it's OK to show this URL type in this window */
+     if (new_window_hint == 0)
+     {
+          if (priv->base.urltype_cb)
+          {
+               new_window = !((priv->base.urltype_cb)(type));
+          }
+     }
+     else
+     {
+          new_window = true;
+     }
+
+     if (!new_window)
+     {
+          gnc_html_cancel (GNC_HTML(self));
+     }
+
+     if (gnc_html_url_handlers)
+     {
+          const gpointer p = g_hash_table_lookup (gnc_html_url_handlers, type);
+          url_handler = reinterpret_cast<GncHTMLUrlCB>(p);
+     }
+
+     if (url_handler)
+     {
+          GNCURLResult result;
+
+          result.load_to_stream = FALSE;
+          result.url_type = type;
+          result.location = nullptr;
+          result.label = nullptr;
+          result.base_type = URL_TYPE_FILE;
+          result.base_location = nullptr;
+          result.error_message = nullptr;
+          result.parent = GTK_WINDOW (priv->base.parent);
+
+          bool ok = url_handler (location, label, new_window, &result);
+          if (!ok)
+          {
+               if (result.error_message)
+               {
+                   gnc_error_dialog (GTK_WINDOW (priv->base.parent), "%s", result.error_message);
+               }
+               else
+               {
+                    /* %s is a URL (some location somewhere). */
+                    gnc_error_dialog (GTK_WINDOW (priv->base.parent), _("There was an error accessing %s."), location);
+               }
+
+               if (priv->base.load_cb)
+               {
+                    priv->base.load_cb (GNC_HTML(self), result.url_type,
+                                        location, label, priv->base.load_cb_data);
+               }
+          }
+          else if (result.load_to_stream)
+          {
+               const char *new_location = result.location ? result.location : location;
+               const char *new_label = result.label ? result.label : label;
+               auto hnode = gnc_html_history_node_new (result.url_type, new_location, new_label);
+
+               gnc_html_history_append (priv->base.history, hnode);
+
+               g_free (priv->base.base_location);
+               priv->base.base_type = result.base_type;
+               priv->base.base_location =
+                    g_strdup (extract_base_name (result.base_type, new_location));
+               DEBUG ("resetting base location to %s",
+                      priv->base.base_location ? priv->base.base_location : "(null)" );
+
+               stream_loaded = load_to_stream (GNC_HTML_WEBVIEW2(self),
+                                               result.url_type,
+                                               new_location, new_label);
+
+               if (stream_loaded && priv->base.load_cb != nullptr)
+               {
+                    priv->base.load_cb (GNC_HTML(self), result.url_type,
+                                        new_location, new_label, priv->base.load_cb_data);
+               }
+          }
+
+          g_free (result.location);
+          g_free (result.label);
+          g_free (result.base_location);
+          g_free (result.error_message);
+
+          return;
+     }
+
+     if (g_strcmp0 (type, URL_TYPE_JUMP) == 0)
+     {
+          /* WebView2 jumps to the anchor on its own */
+     }
+     else if (g_strcmp0 (type, URL_TYPE_SECURE) == 0 ||
+               g_strcmp0 (type, URL_TYPE_HTTP) == 0 ||
+               g_strcmp0 (type, URL_TYPE_FILE) == 0)
+     {
+
+          do
+          {
+               if (g_strcmp0 (type, URL_TYPE_SECURE) == 0)
+               {
+                    if (!https_allowed())
+                    {
+                        gnc_error_dialog (GTK_WINDOW (priv->base.parent), "%s",
+                                           _("Secure HTTP access is disabled. "
+                                             "You can enable it in the Network section of "
+                                             "the Preferences dialog."));
+                         break;
+                    }
+               }
+
+               if (g_strcmp0 (type, URL_TYPE_HTTP) == 0)
+               {
+                    if (!http_allowed())
+                    {
+                        gnc_error_dialog (GTK_WINDOW (priv->base.parent), "%s",
+                                           _("Network HTTP access is disabled. "
+                                             "You can enable it in the Network section of "
+                                             "the Preferences dialog."));
+                         break;
+                    }
+               }
+
+               priv->base.base_type = type;
+
+               if (priv->base.base_location != nullptr)
+                   g_free (priv->base.base_location);
+               priv->base.base_location = extract_base_name (type, location);
+
+               /* FIXME : handle new_window = 1 */
+               gnc_html_history_append (priv->base.history,
+                                        gnc_html_history_node_new (type, location, label));
+               stream_loaded = load_to_stream (GNC_HTML_WEBVIEW2(self),
+                                               type, location, label);
+
+          }
+          while (false);
+     }
+     else
+     {
+          PERR ("URLType %s not supported.", type);
+     }
+
+     if (stream_loaded && priv->base.load_cb != nullptr)
+     {
+          (priv->base.load_cb)(GNC_HTML(self), type, location, label, priv->base.load_cb_data);
+     }
+}
+
+static void
+reload (GncHtml* self, gboolean force_rebuild)
+{
+     g_return_if_fail (self != nullptr);
+     g_return_if_fail (GNC_IS_HTML_WEBVIEW2(self));
+
+     auto priv = GNC_HTML_WEBVIEW2_GET_PRIVATE(self);
+
+     if (force_rebuild)
+     {
+          gnc_html_history_node *n = gnc_html_history_get_current (priv->base.history);
+          if (n != nullptr)
+               gnc_html_show_url (self, n->type, n->location, n->label, 0);
+     }
+     else if (priv->webview)
+          priv->webview->Reload();
+}
+
+GncHtml*
+gnc_html_webview2_new (void) noexcept
+{
+     auto self = static_cast<GncHtmlWebview2*>(g_object_new (GNC_TYPE_HTML_WEBVIEW2, nullptr));
+     return GNC_HTML(self);
+}
+
+static gboolean
+cancel_helper(gpointer key, gpointer value, gpointer user_data)
+{
+     g_free(key);
+     g_list_free((GList *)value);
+     return TRUE;
+}
+
+static void
+cancel (GncHtml* self)
+{
+     g_return_if_fail (self != nullptr);
+     g_return_if_fail (GNC_IS_HTML_WEBVIEW2(self));
+
+     auto priv = GNC_HTML_WEBVIEW2_GET_PRIVATE(self);
+
+     g_hash_table_foreach_remove (priv->base.request_info, cancel_helper, nullptr);
+}
+
+static void
+copy_to_clipboard (GncHtml* self)
+{
+     g_return_if_fail (self != nullptr);
+     g_return_if_fail (GNC_IS_HTML_WEBVIEW2(self));
+
+     auto priv = GNC_HTML_WEBVIEW2_GET_PRIVATE(self);
+     /* WebView2 has no direct "copy selection" API; ask the page's own
+      * script engine to do it instead.
+      */
+     if (priv->webview)
+          priv->webview->ExecuteScript (L"document.execCommand('copy')", nullptr);
+}
+
+static gboolean
+export_to_file (GncHtml* self, const char *filepath)
+{
+     g_return_val_if_fail (self != nullptr, FALSE);
+     g_return_val_if_fail (GNC_IS_HTML_WEBVIEW2(self), FALSE);
+     g_return_val_if_fail (filepath != nullptr, FALSE);
+
+     auto priv = GNC_HTML_WEBVIEW2_GET_PRIVATE(self);
+     if (priv->html_string == nullptr)
+     {
+          return FALSE;
+     }
+     FILE *fh = g_fopen (filepath, "w" );
+     if (fh != nullptr)
+     {
+          gint len = strlen (priv->html_string);
+          gint written = fwrite (priv->html_string, 1, len, fh);
+          fclose (fh);
+
+          if (written != len)
+          {
+               return FALSE;
+          }
+
+          return TRUE;
+     }
+     else
+     {
+          return FALSE;
+     }
+}
+
+/* Prints the current page.
+ *
+ * ShowPrintUI (the interactive OS print dialog) is declared on
+ * ICoreWebView2_16; PrintToPdf is declared on ICoreWebView2_7 (verified
+ * against the installed WebView2 SDK header -- these version numbers
+ * are not guessed). ICoreWebView2_16 inherits from ICoreWebView2_7, so
+ * a runtime new enough for ShowPrintUI also has PrintToPdf; a second,
+ * lower QI tier covers runtimes new enough for PrintToPdf but too old
+ * for ShowPrintUI.
+ */
+static void
+print (GncHtml* self, const gchar* jobname)
+{
+     g_return_if_fail (self != nullptr);
+     g_return_if_fail (GNC_IS_HTML_WEBVIEW2 (self));
+
+     auto priv = GNC_HTML_WEBVIEW2_GET_PRIVATE (self);
+     if (!priv->webview)
+          return;
+
+     ICoreWebView2_16* webview16 = nullptr;
+     HRESULT hr = priv->webview->QueryInterface (IID_ICoreWebView2_16,
+                                                  reinterpret_cast<void**> (&webview16));
+     if (SUCCEEDED (hr) && webview16)
+     {
+          webview16->ShowPrintUI (COREWEBVIEW2_PRINT_DIALOG_KIND_SYSTEM);
+          webview16->Release ();
+          return;
+     }
+
+     ICoreWebView2_7* webview7 = nullptr;
+     hr = priv->webview->QueryInterface (IID_ICoreWebView2_7,
+                                         reinterpret_cast<void**> (&webview7));
+     if (!SUCCEEDED (hr) || !webview7)
+     {
+          gnc_error_dialog (GTK_WINDOW (priv->base.parent), "%s",
+                            _("Your WebView2 Runtime is too old to print. "
+                              "Please update it via Windows Update."));
+          return;
+     }
+
+     gchar *pdf_path = g_str_has_suffix (jobname, ".pdf") ? g_strdup (jobname)
+                                                           : g_strconcat (jobname, ".pdf", nullptr);
+     gunichar2* wpath = g_utf8_to_utf16 (pdf_path, -1, nullptr, nullptr, nullptr);
+     webview7->PrintToPdf (reinterpret_cast<LPCWSTR> (wpath), nullptr, nullptr);
+     g_free (wpath);
+     webview7->Release ();
+
+     gchar *msg = g_strdup_printf (
+          _("Your WebView2 Runtime is too old to show a print dialog. "
+            "The report was saved as a PDF instead: %s"), pdf_path);
+     gnc_info_dialog (GTK_WINDOW (priv->base.parent), "%s", msg);
+     g_free (msg);
+     g_free (pdf_path);
+}
+
+static void
+set_parent (GncHtml* self, GtkWindow* parent)
+{
+     g_return_if_fail (self != nullptr);
+     g_return_if_fail (GNC_IS_HTML_WEBVIEW2(self));
+
+     auto priv = GNC_HTML_WEBVIEW2_GET_PRIVATE(self);
+     priv->base.parent = GTK_WIDGET(parent);
+}
+
+static void
+default_zoom_changed(gpointer prefs, gchar *pref, gpointer user_data)
+{
+     g_return_if_fail(user_data != nullptr);
+
+     GncHtmlWebview2* self = GNC_HTML_WEBVIEW2(user_data);
+     GncHtmlWebview2Private* priv = GNC_HTML_WEBVIEW2_GET_PRIVATE(self);
+     gdouble zoom = gnc_prefs_get_float (GNC_PREFS_GROUP_GENERAL_REPORT,
+                                         GNC_PREF_RPT_DFLT_ZOOM);
+     if (priv->controller)
+          priv->controller->put_ZoomFactor (zoom);
+}
diff --git a/gnucash/html/gnc-html-webview2.hpp b/gnucash/html/gnc-html-webview2.hpp
new file mode 100644
index 0000000000..eb28f6318c
--- /dev/null
+++ b/gnucash/html/gnc-html-webview2.hpp
@@ -0,0 +1,63 @@
+/********************************************************************
+ * gnc-html-webview2.hpp -- display html with gnc special tags        *
+ * Copyright (C) 2026 John Ralls <jralls at ceridwen.us>                *
+ *                                                                  *
+ * This program is free software; you can redistribute it and/or    *
+ * modify it under the terms of the GNU General Public License as   *
+ * published by the Free Software Foundation; either version 2 of   *
+ * the License, or (at your option) any later version.              *
+ *                                                                  *
+ * This program is distributed in the hope that it will be useful,  *
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of   *
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the    *
+ * GNU General Public License for more details.                     *
+ *                                                                  *
+ * You should have received a copy of the GNU General Public License*
+ * along with this program; if not, contact:                        *
+ *                                                                  *
+ * Free Software Foundation           Voice:  +1-617-542-5942       *
+ * 51 Franklin Street, Fifth Floor    Fax:    +1-617-542-2652       *
+ * Boston, MA  02110-1301,  USA       gnu at gnu.org                   *
+\********************************************************************/
+
+/* GncHtml backend that hosts Microsoft Edge WebView2 as a native child
+ * window parented to a plain GTK widget's own natively-backed HWND
+ * (see gdk_window_ensure_native()). Windows-only; selected instead of
+ * gnc-html-webkit1/2 when the WEBVIEW2 build option is set. */
+
+#ifndef GNC_HTML_WEBVIEW2_H
+#define GNC_HTML_WEBVIEW2_H
+
+#include <glib-object.h>
+#include "gnc-html.h"
+
+G_BEGIN_DECLS
+
+#define GNC_TYPE_HTML_WEBVIEW2       (gnc_html_webview2_get_type())
+#define GNC_HTML_WEBVIEW2(o)         (G_TYPE_CHECK_INSTANCE_CAST ((o), GNC_TYPE_HTML_WEBVIEW2, GncHtmlWebview2))
+#define GNC_HTML_WEBVIEW2_CLASS(k)   (G_TYPE_CHECK_CLASS_CAST((k), GNC_TYPE_HTML_WEBVIEW2, GncHtmlWebview2Class))
+#define GNC_IS_HTML_WEBVIEW2(o)      (G_TYPE_CHECK_INSTANCE_TYPE((o), GNC_TYPE_HTML_WEBVIEW2))
+#define GNC_IS_HTML_WEBVIEW2_CLASS(k)   (G_TYPE_CHECK_CLASS_TYPE((k), GNC_TYPE_HTML_WEBVIEW2))
+#define GNC_HTML_WEBVIEW2_GET_CLASS(o)  (G_TYPE_INSTANCE_GET_CLASS((o), GNC_TYPE_HTML_WEBVIEW2, GncHtmlWebview2Class))
+
+struct GncHtmlWebview2Private;
+
+struct GncHtmlWebview2
+{
+    GncHtml parent_instance;
+
+    /*< private >*/
+    GncHtmlWebview2Private* priv;
+};
+
+struct GncHtmlWebview2Class
+{
+    GncHtmlClass parent_class;
+};
+
+GType gnc_html_webview2_get_type( void );
+GncHtml* gnc_html_webview2_new( void ) NOEXCEPT;
+
+G_END_DECLS
+
+#endif // GNC_HTML_WEBVIEW2_H
diff --git a/po/POTFILES.in b/po/POTFILES.in
index 1c6e108f9e..468820330e 100644
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -298,6 +298,7 @@ gnucash/html/gnc-html-factory.cpp
 gnucash/html/gnc-html-history.cpp
 gnucash/html/gnc-html-webkit1.cpp
 gnucash/html/gnc-html-webkit2.cpp
+gnucash/html/gnc-html-webview2.cpp
 gnucash/html/html.scm
 gnucash/import-export/aqb/assistant-ab-initial.c
 gnucash/import-export/aqb/assistant-ab-initial.glade



Summary of changes:
 CMakeLists.txt                                     |   41 +-
 common/cmake_modules/GncAddSchemeTargets.cmake     |   12 +-
 common/cmake_modules/GncAddTest.cmake              |    8 +-
 common/config.h.cmake.in                           |    4 +-
 gnucash/gnome/gnc-plugin-page-report.cpp           |   42 +-
 gnucash/gnome/gnc-plugin-page-report.h             |    8 +-
 gnucash/gnome/gnc-plugin-report-system.c           |   21 +-
 gnucash/gnome/top-level.c                          |    2 +
 gnucash/html/CMakeLists.txt                        |   19 +-
 gnucash/html/gnc-html-factory.cpp                  |    4 +
 gnucash/html/gnc-html-webkit.hpp                   |    4 +-
 gnucash/html/gnc-html-webkit1.cpp                  | 1362 ----------------
 gnucash/html/gnc-html-webkit2.cpp                  |   33 +-
 gnucash/html/gnc-html-webview2-p.hpp               |   86 +
 gnucash/html/gnc-html-webview2.cpp                 | 1640 ++++++++++++++++++++
 ...{gnc-html-webkit1.hpp => gnc-html-webview2.hpp} |   39 +-
 gnucash/html/gnc-html.cpp                          |   17 +-
 gnucash/html/gnc-html.h                            |   30 +-
 libgnucash/backend/dbi/test/CMakeLists.txt         |    2 +-
 po/POTFILES.in                                     |    2 +-
 20 files changed, 1839 insertions(+), 1537 deletions(-)
 delete mode 100644 gnucash/html/gnc-html-webkit1.cpp
 create mode 100644 gnucash/html/gnc-html-webview2-p.hpp
 create mode 100644 gnucash/html/gnc-html-webview2.cpp
 rename gnucash/html/{gnc-html-webkit1.hpp => gnc-html-webview2.hpp} (55%)



More information about the gnucash-changes mailing list