Replace IE WebBrowser output renderer with WebView2 (Chromium) - #3592
Replace IE WebBrowser output renderer with WebView2 (Chromium)#3592jmcouffin wants to merge 12 commits into
Conversation
- Introduced ScriptWebView class to manage WebView2 lifecycle and script execution. - Updated ScriptOutput to utilize WebView2 for rendering, replacing legacy WebBrowser control. - Added methods for JavaScript evaluation and renderer version retrieval. - Refactored output handling in PyRevitOutputWindow to accommodate new rendering engine. - Improved code formatting and consistency across output module.
|
Unable to trigger custom agent "Code Reviewer". You have run out of credits 😔 |
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
romangolev
left a comment
There was a problem hiding this comment.
Tested with Revit 2021 - 2026
┌───────┬────────┬──────────────┬─────────────────┬────────────┐
│ Revit │ TFM │ Bundled CEF │ Profile created │ Outcome │
├───────┼────────┼──────────────┼─────────────────┼────────────┤
│ 2021 │ net48 │ Chromium 65 │ 20:50:33 │ crash +16s │
├───────┼────────┼──────────────┼─────────────────┼────────────┤
│ 2022 │ net48 │ Chromium 65 │ 21:27:36 │ crash +21s │
├───────┼────────┼──────────────┼─────────────────┼────────────┤
│ 2023 │ net48 │ Chromium 92 │ 21:29:39 │ crash +21s │
├───────┼────────┼──────────────┼─────────────────┼────────────┤
│ 2024 │ net48 │ Chromium 105 │ 21:31:38 │ crash +21s │
├───────┼────────┼──────────────┼─────────────────┼────────────┤
│ 2025 │ net8.0 │ Chromium 119 │ 20:57:18 │ clean │
├───────┼────────┼──────────────┼─────────────────┼────────────┤
│ 2026 │ net8.0 │ no libcef │ 21:10:42 │ clean │
└───────┴────────┴──────────────┴─────────────────┴────────────┘
I have a full debug mode and the highest verbose turned on at the moment of testing and a crash seems to be happening at the startup. Without debug mode it starts up fine. I'll try to apply a fix to it.
The previous implementation pumped a nested dispatcher frame inside WebView2 init to wait for the core, which re-entered Revit's message loop during session load and terminated the process. Schedule initialization via BeginInvoke, defer to the control's Loaded event when it isn't in the visual tree yet, and flush output from a DocumentReady event instead of polling. Reserve a blocking WaitUntilReady / EnsureDocumentReadyBlocking path for user commands only. Guard flush exceptions, log lifecycle events through NLog, and drop payloads with a one-time warning when the renderer is permanently broken.
Pull the inline rationale comments out of the ScriptConsole flush paths and the ScriptWebView lifecycle into Google-style <summary>, Important, Warning, and Invariant sections so the lifecycle invariants and re-entrancy hazards are documented where readers actually look. Also drop the now-unused waitForBrowser parameter from FlushPendingEntries, since every caller is fire-and-forget after the non-blocking init rework.
Direct _webView.PostScript calls were silently dropped whenever the document had not finished initializing yet — scroll-to-bottom, set-element-visibility, progress updates, inline-wait, and script/HTML injection all hit that path during session load. Add a PostScriptWhenReady helper that queues the script under the existing _pendingLock when IsDocumentReady is false and replays it in submission order from both DocumentReady and SyncFlushBlocking. Switch the affected callers over to the helper and drop the now-redundant EnsureDocumentReady calls they used to need.
|
@jmcouffin I got the crash settled, Do we ship WebView2 dlls straight away? I believe there is nothing much we can do about except providing best effort. Revit 2026+ supports Dependency isolation for add-ins. I believe that this is something we are looking forward to use in order to minimize those possible collisions either in scope of this PR or in a scope of the follow-up PR |
|
|
@romangolev Thanks for digging into this. You're right—we can't guarantee DLL resolution across add-ins without isolation. The Evergreen approach minimizes conflicts in practice (auto-updates, single system install), but it's not foolproof. For 7.0, I'm comfortable shipping with this as a known limitation and adding it to the release notes. We should plan dependency isolation for 2026+ in 7.1. For now, let's get this merged and gather real-world feedback on collisions. If we start seeing reports, we escalate the isolation work sooner. |
Summary
The script output window (
ScriptConsole) now renders with Microsoft EdgeWebView2 (Chromium, Evergreen runtime) instead of the legacy IE-based
System.Windows.Forms.WebBrowsercontrol. All output-window behavior —streaming stdout/stderr, html/markdown printing, tables, charts, progress,
freeze/unfreeze, save/copy/print,
revit://deep links, stdin — is preservedbehind the same public API, with a small set of intentional breaking changes
documented below.
Validated end-to-end on Revit 2023, 2024, 2025, 2026 and 2027 with the new
WebView2 Checkdiagnostic tool (17/17 checks on every version), plus manualpasses for streaming, audit-all dashboards, Chart.js rendering, deep links,
custom stylesheets, and both IronPython and CPython engines.
The critical finding: CEF ↔ WebView2 window-class collision
Revit hosts its own Chromium via CefSharp (
libcef.dll) for internal UI(Home page, Model Browser). WebView2 is a second Chromium embedded in the same
process. When the two are built on different Chromium major versions, both
register the same Win32 window classes (
Chrome_WidgetWin_0, …) in the hostprocess;
CreateWindowExthen fails for one of them and libcef fails a windowcreation CHECK — surfacing as:
…followed by a hang or hard crash of Revit. This took Revit 2023/2024/2025
down (older bundled CEF), while 2026/2027 (newer CEF, Chromium versions close
to the runtime's 151) coexisted fine — which made the failure look
version-flaky before the mechanism was identified.
Documented upstream:
Fix (Microsoft's official opt-in workaround): pass
--edge-webview-unique-window-classviaCoreWebView2EnvironmentOptions.AdditionalBrowserArguments.WebView2 then suffixes its app-process window class names
(
Chrome_WidgetWin_0_EmbeddedBrowserWebView), removing the collisionregardless of the host CEF's Chromium version. Implemented as the default in
ScriptWebView.BuildBrowserArguments(); extra diagnostic flags can be appendedat runtime via the
PYREVIT_WEBVIEW2_ARGSenvironment variable.Other engineering changes
Per-Revit-version browser profiles
%LocalAppData%\pyRevit\WebView2\<year>(parsed from the runtime assemblyname). Prevents multiple simultaneously-running Revit versions from attaching
to one shared browser process — a secondary source of startup instability.
Synchronous API over an asynchronous engine
The output API is synchronous and is called from engine threads, while
WebView2 is strictly async.
ScriptWebViewmarshals every operation onto thewindow dispatcher and blocks via a nested message pump (
DispatcherFrame),with ordered fire-and-forget inserts for the hot print path and pump-waiting
evaluations for DOM reads. Output entries are buffered per window and flushed
as single
insertAdjacentHTMLcalls with JS-side stick-to-bottom scrolling.Window visibility before controller creation
EnsureCoreWebView2Asyncdefers until the WPF window has been shown (therenderer's child HWND does not exist before
Show()).EnsureDocumentReadysurfaces the window exactly like the first printed entry would, and
initialization failures are recorded so later calls fail fast instead of
re-waiting.
Chromium behavior differences handled
<script>inserted viainnerHTMLnever executes inChromium.
inject_scriptnow builds realcreateElement('script')elements(charts/Chart.js depend on this).
.htmlfile instead ofNavigateToString, because Chromium blocksfile://subresources (customstylesheets,
print_imagefile URIs) from opaque origins.[text](url); links are now converted (before emphasis patterns, so URLsare not mangled) and open in the default browser via the navigation policy.
WebBrowser.NavigatingtoCoreWebView2.NavigationStarting(http(s) → shell,revit://→ScriptConsoleUtils.ProcessUrl,file://→ in-window).still eats the keystrokes) and routes to
document.execCommand.CoreWebView2.ShowPrintUI(); copy-all readsbody.innerText.Runtime availability handling
If the Evergreen runtime is missing, the window shows an explanatory page and
offers to open the Evergreen bootstrapper download. Initialization failures
are cached so subsequent calls fail fast.
Breaking changes (intentional, clean break)
output.renderer(Python) raisesNotImplementedErrorwith migrationguidance;
ScriptOutput.rendererandScriptConsole.ActiveDocument(raw IE DOM leakage) are removed.
ScriptConsole.RendererVersionnow reports the WebView2/Chromium runtimeversion; the
rendererversionpage meta carries it (theX-UA-CompatibleIE meta is gone).print_mdmarkdown links now render as anchors (new capability, butscripts that relied on literal
[text](url)output will see anchors).pyRevitLabs.Emojisdictionary is the conversionsource and does not cover every name (
white_check_mark/xare absent;heavy_check_mark/cross_markwork). Chromium renders literal Unicodeemoji perfectly — prefer those in new scripts.
output.evaluate_js(script),output.renderer_engine,output.renderer_versionare new public API (used by the preflight check tool, usefulfor extension authors).
Packaging & dependencies
Microsoft.Web.WebView21.0.4129.50 vendored intodev/libs/netfxanddev/libs/netcore(managedCore/WinForms/Wpf+ x64 nativeWebView2Loader.dll), referenced via HintPath in the RuntimeDirectory.Build.targets; shipped by the existingdev\libswildcard copy.Dependency_AddWebView2so installed deployments always have the Evergreenruntime. WiX picks the DLLs up automatically (HeatDirectory harvest of
bin\).runtime get the download dialog, not silent IE rendering.
New diagnostics tooling
pyRevitDevtab → Debug panel → WebView2 Check: 17 automated checksagainst the live document — engine identity, runtime version, Chromium user
agent, head meta,
inject_to_head/body, injected script execution,add_style, markdown link conversion,revit://anchors, progress-bar DOM,legacy
rendererremoval, user-data folder, and emoji rendering through allthree paths (literal Unicode, Segoe UI Emoji font, DLL shortcodes). Results
print as a pass/fail table.
Testing
build → dotnet run -c Release -- ci)WebView2 Checktoolrevit://deep links (select / button / print)set_stylesheet)Rollout notes
--edge-webview-unique-window-classswitch is honored by current runtimes and ignored (harmlessly) by older ones.
%LocalAppData%\pyRevit\WebView2\<year>(~tens of MB); stale profiles fromremoved versions can be deleted manually.
retest with this branch — the window-class fix specifically addresses the
libcef break-exception crashes seen during validation.