nvaccess / nvda

NVDA, the free and open source Screen Reader for Microsoft Windows
https://www.nvaccess.org/
Other
2.1k stars 634 forks source link

Browse mode does not activate, and is not available, in wx.html2.WebView instances #17273

Open XLTechie opened 2 weeks ago

XLTechie commented 2 weeks ago

The only other mention of this I could find, was by @JulienCochuyt in https://github.com/nvaccess/nvda/issues/10838#issuecomment-595080150, back in 2020; and someone talking about it on the wx-py mailing list, which got no response in five years.

Steps to reproduce:

Run any application which uses a wx.html2.WebView. I will provide a sample shortly.

Actual behavior:

When attempting to focus on and interact with the HTML document,, its content is not spoken or able to be navigated. The content is definitely there, as NVDA+R can confirm, but normal navigation will never find it. If you force the focus (as described by @JulienCochuyt, or by mouse as I will show with an example app), it can be made to work. But that method is highly un-ideal.

Expected behavior:

Preferably, NVDA should switch into browse mode automatically, or should at least be able to manually (although most users won't know to do so).

NVDA logs, crash dumps and other attachments:

System configuration

NVDA installed/portable/running from source:

Installed

NVDA version:

2024.3.1

Windows version:

11, though this was known in 10.

Name and version of other software in use when reproducing the issue:

Python 3.11, WX 4.2.

Other information about your system:

Other questions

Does the issue still occur after restarting your computer?

Yes

Have you tried any other versions of NVDA? If so, please report their behaviors.

This has been going on since at least 2019.3, and probably since wx.html2 became available.

If NVDA add-ons are disabled, is your problem still occurring?

Not relevant, but yes.

Does the issue still occur after you run the COM Registration Fixing Tool in NVDA's tools menu?

Not relevant, but yes.

hwf1324 commented 2 weeks ago

wxPython demo:

#!/usr/bin/env python
import os

import wx
import wx.html2 as webview

# WebView Backends
backends = [
    (webview.WebViewBackendEdge, 'WebViewBackendEdge'),
    (webview.WebViewBackendIE, 'WebViewBackendIE'),
    (webview.WebViewBackendWebKit, 'WebViewBackendWebKit'),
    (webview.WebViewBackendDefault, 'WebViewBackendDefault'),
]

#----------------------------------------------------------------------

class TestPanel(wx.Panel):
    def __init__(self, parent, log, frame=None):
        self.log = log
        wx.Panel.__init__(self, parent, -1)

        self.current = "http://wxPython.org"
        self.frame = frame
        if frame:
            self.titleBase = frame.GetTitle()

        sizer = wx.BoxSizer(wx.VERTICAL)
        btnSizer = wx.BoxSizer(wx.HORIZONTAL)

        # The Internet Explorer emulation level is a persistent per-user and
        # per-application value.
        webview.WebView.MSWSetEmulationLevel(webview.WEBVIEWIE_EMU_IE11)

        # If you would like to reset it back to the default (no-emulation) then
        # it can be done by calling this:
        # webview.WebView.MSWSetEmulationLevel(webview.WEBVIEWIE_EMU_DEFAULT)

        # Find an available backend
        backend = None
        for id, name in backends:
            available = webview.WebView.IsBackendAvailable(id)
            log.write("Backend 'wx.html2.{}' availability: {}\n".format(name, available))
            if available and backend is None:
                backend = id
        log.write("Using backend: '{}'\n".format(str(backend, 'ascii')))

        # Create the WebView
        self.wv = webview.WebView.New(self, backend=backend)

        self.Bind(webview.EVT_WEBVIEW_NAVIGATING, self.OnWebViewNavigating, self.wv)
        self.Bind(webview.EVT_WEBVIEW_LOADED, self.OnWebViewLoaded, self.wv)

        btn = wx.Button(self, -1, "Open", style=wx.BU_EXACTFIT)
        self.Bind(wx.EVT_BUTTON, self.OnOpenButton, btn)
        btnSizer.Add(btn, 0, wx.EXPAND|wx.ALL, 2)

        btn = wx.Button(self, -1, "<--", style=wx.BU_EXACTFIT)
        self.Bind(wx.EVT_BUTTON, self.OnPrevPageButton, btn)
        btnSizer.Add(btn, 0, wx.EXPAND|wx.ALL, 2)
        self.Bind(wx.EVT_UPDATE_UI, self.OnCheckCanGoBack, btn)

        btn = wx.Button(self, -1, "-->", style=wx.BU_EXACTFIT)
        self.Bind(wx.EVT_BUTTON, self.OnNextPageButton, btn)
        btnSizer.Add(btn, 0, wx.EXPAND|wx.ALL, 2)
        self.Bind(wx.EVT_UPDATE_UI, self.OnCheckCanGoForward, btn)

        btn = wx.Button(self, -1, "Stop", style=wx.BU_EXACTFIT)
        self.Bind(wx.EVT_BUTTON, self.OnStopButton, btn)
        btnSizer.Add(btn, 0, wx.EXPAND|wx.ALL, 2)

        btn = wx.Button(self, -1, "Refresh", style=wx.BU_EXACTFIT)
        self.Bind(wx.EVT_BUTTON, self.OnRefreshPageButton, btn)
        btnSizer.Add(btn, 0, wx.EXPAND|wx.ALL, 2)

        txt = wx.StaticText(self, -1, "Location:")
        btnSizer.Add(txt, 0, wx.CENTER|wx.ALL, 2)

        self.location = wx.ComboBox(
            self, -1, "", style=wx.CB_DROPDOWN|wx.TE_PROCESS_ENTER)
        self.location.AppendItems(['http://wxPython.org',
                                   'http://wxwidgets.org',
                                   'http://google.com'])
        self.Bind(wx.EVT_COMBOBOX, self.OnLocationSelect, self.location)
        self.location.Bind(wx.EVT_TEXT_ENTER, self.OnLocationEnter)
        btnSizer.Add(self.location, 1, wx.EXPAND|wx.ALL, 2)

        sizer.Add(btnSizer, 0, wx.EXPAND)
        sizer.Add(self.wv, 1, wx.EXPAND)
        self.SetSizer(sizer)

        self.wv.LoadURL(self.current)

    def ShutdownDemo(self):
        # put the frame title back
        if self.frame:
            self.frame.SetTitle(self.titleBase)

    # WebView events
    def OnWebViewNavigating(self, evt):
        # this event happens prior to trying to get a resource
        if evt.GetURL() == 'http://www.microsoft.com/':
            if wx.MessageBox("Are you sure you want to visit Microsoft?",
                             style=wx.YES_NO|wx.ICON_QUESTION) == wx.NO:
                # This is how you can cancel loading a page.
                evt.Veto()

    def OnWebViewLoaded(self, evt):
        # The full document has loaded
        self.current = evt.GetURL()
        self.location.SetValue(self.current)

    # Control bar events
    def OnLocationSelect(self, evt):
        url = self.location.GetStringSelection()
        self.log.write('OnLocationSelect: %s\n' % url)
        self.wv.LoadURL(url)

    def OnLocationEnter(self, evt):
        url = self.location.GetValue()
        self.location.Append(url)
        self.wv.LoadURL(url)

    def OnOpenButton(self, event):
        dlg = wx.TextEntryDialog(self, "Open Location",
                                "Enter a full URL or local path",
                                self.current, wx.OK|wx.CANCEL)
        dlg.CentreOnParent()

        if dlg.ShowModal() == wx.ID_OK:
            self.current = dlg.GetValue()
            self.wv.LoadURL(self.current)

        dlg.Destroy()

    def OnPrevPageButton(self, event):
        self.wv.GoBack()

    def OnNextPageButton(self, event):
        self.wv.GoForward()

    def OnCheckCanGoBack(self, event):
        event.Enable(self.wv.CanGoBack())

    def OnCheckCanGoForward(self, event):
        event.Enable(self.wv.CanGoForward())

    def OnStopButton(self, evt):
        self.wv.Stop()

    def OnRefreshPageButton(self, evt):
        self.wv.Reload()

#----------------------------------------------------------------------

def runTest(frame, nb, log):
    win = TestPanel(nb, log)
    return win

#----------------------------------------------------------------------

overview = """<html><body>
<h2><center>DemoName</center></h2>

Say something nice here

</body></html>
"""

if __name__ == '__main__':
    import sys,os
    import run
    run.main(['', os.path.basename(sys.argv[0])] + sys.argv[1:])

I can't reproduce it in this demo, when I press Tab to cut to the page I can view it normally.

XLTechie commented 1 week ago

@hwf1324 Thank you for your demo. Unfortunately, it can not run as-is, because of the lack of the run package. I have tried installing the run package from pip, but it is broken. Is this something custom of yours?

I will attempt to rewrite with what I think run.main might be doing.

hwf1324 commented 1 week ago

No, this is the official demo provided by wxPython. this runs in NVDA's virtual environment.

https://extras.wxpython.org/wxPython4/extras/4.2.2/wxPython-demo-4.2.2.tar.gz

LeonarddeR commented 1 week ago

I recall this is a focus issue in wx, where the IA focus is set on the web view itself, not on the focused entry within the web view. We might need aa focus redirect here.

XLTechie commented 1 week ago

@hwf1324 I have built a standalone test program from the demo code you provided, and a copy of run.py's contents from the demo archive. Note that for simplicity, I merged it into a single source file, and removed the parts of the code used for determining and setting up the demo module.

Here is that sample. Browse mode does not activate automatically for me, under NVDA 2024.3.1. When tabbed to what NVDA speaks as "WX Webview 2", I can use object navigation and object review to access the loaded HTML content, but never does it enter browse mode.

html2 test from demo.zip

hwf1324 commented 1 week ago

@XLTechie This is strange, I do have a problem when I run tView.exe, but it works fine when I run tView.pyw from the source. My version of wxPython is 4.2.2