Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
ae70597
Webview browseable message prototype
LeonarddeR Sep 8, 2025
426fc6f
Fixings
LeonarddeR May 28, 2026
5ceab04
Rework browseableMessage to use wx.html2.WebView via MessageDialog
LeonarddeR May 28, 2026
5b36a82
Fix
LeonarddeR May 29, 2026
d957f44
Refactor browseable message into an HtmlMessageDialog subclass
LeonarddeR May 29, 2026
b13909b
Align browseable message copy with the About dialog
LeonarddeR May 29, 2026
d19af95
Pre-commit auto-fix
pre-commit-ci[bot] May 29, 2026
926c251
Add change log entries for the browseable message rework
LeonarddeR May 29, 2026
c4d27ec
Fixup
LeonarddeR May 30, 2026
376ceac
Add system tests
LeonarddeR May 30, 2026
6d0f08d
Pre-commit auto-fix
pre-commit-ci[bot] May 30, 2026
84b83d2
HtmlMessageDialog: allow showing without buttons; fix Escape
LeonarddeR May 30, 2026
2ed6d5b
Pre-commit auto-fix
pre-commit-ci[bot] May 30, 2026
3ef6466
Add note about edge and wx python limitaitons
LeonarddeR May 30, 2026
837c4b4
Ensure IE11 emu
LeonarddeR May 30, 2026
811ca7a
Pre-commit auto-fix
pre-commit-ci[bot] May 30, 2026
9515b03
Remove not supported IE emulation level option
LeonarddeR Jun 1, 2026
b5758eb
Merge remote-tracking branch 'origin/master' into browsableWebview
LeonarddeR Jun 1, 2026
d26c8e8
Merge remote-tracking branch 'origin/master' into browsableWebview
LeonarddeR Jun 2, 2026
4a0b336
Add deprecation strategy
LeonarddeR Jun 2, 2026
2d7fd1d
Review actions
LeonarddeR Jun 2, 2026
cdf0fe6
Remove Show override
LeonarddeR Jun 8, 2026
4b4897e
Ensure that _onNavigating supports an override to Edge in future
LeonarddeR Jun 10, 2026
46664f7
Merge remote-tracking branch 'origin/master' into browsableWebview
LeonarddeR Jun 17, 2026
6596d76
Specify deprecated values inline
LeonarddeR Jun 17, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Fixings
  • Loading branch information
LeonarddeR committed May 29, 2026
commit 426fc6f1119afdc37bd71b8435b3fafad10f019c
5 changes: 3 additions & 2 deletions source/gui/message.py
Original file line number Diff line number Diff line change
Expand Up @@ -451,6 +451,7 @@ def __init__(
# Scafold the dialog.
mainSizer = self._mainSizer = wx.BoxSizer(wx.VERTICAL)
contentsSizer = self._contentsSizer = guiHelper.BoxSizerHelper(parent=self, orientation=wx.VERTICAL)
self._isHtmlMessage = isHtmlMessage
if isHtmlMessage:
messageControl = self._messageControl = WebView.New(self)
else:
Expand Down Expand Up @@ -669,7 +670,7 @@ def setMessage(self, message: str) -> Self:
:param message: New message to show.
:return: Updated instance for chaining.
"""
if isinstance(self._messageControl, WebView):
if self._isHtmlMessage:
self._messageControl.SetPage(message, "")
else:
# Use SetLabelText to avoid ampersands being interpreted as accelerators.
Expand Down Expand Up @@ -963,7 +964,7 @@ def _realizeLayout(self) -> None:
if gui._isDebug():
startTime = time.time()
log.debug("Laying out message dialog")
if isinstance(self._messageControl, wx.StaticText):
if not self._isHtmlMessage:
self._messageControl.Wrap(self.scaleSize(self.GetSize().Width))
self._mainSizer.Fit(self)
if self.Parent == gui.mainFrame:
Expand Down
95 changes: 24 additions & 71 deletions source/ui.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,20 +30,6 @@
"""


# From urlmon.h
URL_MK_UNIFORM = 1

# Dialog box properties
DIALOG_OPTIONS = "resizable:yes;help:no"

# dwDialogFlags for ShowHTMLDialogEx from mshtmhst.h
HTMLDLG_NOUI = 0x0010
HTMLDLG_MODAL = 0x0020
HTMLDLG_MODELESS = 0x0040
HTMLDLG_PRINT_TEMPLATE = 0x0080
HTMLDLG_VERIFY = 0x0100

Comment thread
LeonarddeR marked this conversation as resolved.

def _warnBrowsableMessageNotAvailableOnSecureScreens(title: str | None = None) -> None:
"""Warn the user that a browsable message could not be shown on a secure screen (sign-on screen / UAC
prompt).
Expand Down Expand Up @@ -73,51 +59,7 @@ def _warnBrowsableMessageNotAvailableOnSecureScreens(title: str | None = None) -
" such as the sign-on screen or UAC prompt.",
).format(title=title)

import gui # Late import to prevent circular dependency.
import wx # Late import to prevent circular dependency.

log.debug("Presenting browsable message unavailable warning.")
wx.CallAfter(
gui.messageBox,
browsableMessageUnavailableMsg,
# Translators: This is the title for a warning dialog, shown if NVDA cannot open a browsable message.
caption=_("Feature unavailable."),
style=wx.ICON_ERROR | wx.OK,
)


def _warnBrowsableMessageComponentFailure(title: str | None = None) -> None:
"""Warn the user that a browsable message could not be shown because of a component failure.

:param title: If provided, the title of the browsable message to give the user more context.
"""
log.warning(
"A browsable message could not be shown because of a component failure."
f" Attempted to open message with title: {title!r}",
)

if not title:
browsableMessageUnavailableMsg: str = _(
# Translators: This is the message for a warning shown if NVDA cannot open a browsable message window
# because of a component failure.
"An error has caused this feature to be unavailable at this time. "
"Restarting NVDA or Windows may solve this problem.",
)
else:
browsableMessageUnavailableMsg: str = _(
# Translators: This is the message for a warning shown if NVDA cannot open a browsable message window
# because of a component failure. This prompt includes the title
# of the Window that could not be opened for context.
# The {title} will be replaced with the title.
# The title may be something like "Formatting".
"An error has caused this feature ({title}) to be unavailable at this time. "
"Restarting NVDA or Windows may solve this problem.",
).format(title=title)

log.debug("Presenting browsable message unavailable warning.")
import gui # Late import to prevent circular dependency.
import wx # Late import to prevent circular dependency.

wx.CallAfter(
gui.messageBox,
browsableMessageUnavailableMsg,
Expand All @@ -135,6 +77,19 @@ def browseableMessage(
copyButton: bool = False,
sanitizeHtmlFunc: Callable[[str], str] = nh3.clean,
) -> None:
"""Present a message to the user that can be read in browse mode.
The message will be presented in an HTML document.

:param message: The message in either html or text.
:param title: The title for the message, defaults to "NVDA Message".
:param isHtml: Whether the message is html, defaults to False.
:param closeButton: Whether to include a "close" button, defaults to False.
:param copyButton: Whether to include a "copy" (to clipboard) button, defaults to False.
:param sanitizeHtmlFunc: How to sanitize the html message, if isHtml is True.
Defaults to `nh3.clean` with default arguments.
Ensure to sanitize the html message if the source of it could be untrusted.
Any translatable string, or user generated content should be sanitized.
"""
if isRunningOnSecureDesktop():
_warnBrowsableMessageNotAvailableOnSecureScreens(title)
return
Expand All @@ -147,17 +102,13 @@ def browseableMessage(
messageSanitized = f"<pre>{escape(message)}</pre>"
else:
messageSanitized = sanitizeHtmlFunc(message)
templatedMessage = f"""
<!doctype html>
<HTML style="width : 350; height: 300">
<HEAD>
<TITLE>{title}</TITLE>
</HEAD>
<body style="margin:1em">
<div id="messageDiv">{messageSanitized}</div>
</body>
</html>
"""
templatedMessage = (
f"<!doctype html>"
f"<html style=\"width: 350px; height: 300px\">"
f"<head><title>{escape(title)}</title></head>"
f"<body style=\"margin: 1em\"><div id=\"messageDiv\">{messageSanitized}</div></body>"
f"</html>"
)

# --- build the dialog ---
dialog = MessageDialog(
Expand All @@ -168,11 +119,11 @@ def browseableMessage(
isHtmlMessage=True,
)

if closeButton:
if closeButton or not copyButton:
dialog.addCloseButton()
if copyButton:

def doCopy(payload):
def doCopy(evt):
if wx.TheClipboard.Open():
wx.TheClipboard.SetData(wx.TextDataObject(message))
wx.TheClipboard.Close()
Expand All @@ -182,7 +133,9 @@ def doCopy(payload):

dialog.addButton(ReturnCode.CUSTOM_1, label=_("&Copy"), callback=doCopy, closesDialog=False)

gui.mainFrame.prePopup()
dialog.Show()
gui.mainFrame.postPopup()
Comment thread
seanbudd marked this conversation as resolved.


def message(
Expand Down