Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Electron enables accessibility mode all the time with touch monitors (100% CPU usage in "Other") #7208

Closed
alexdima opened this issue Sep 14, 2016 · 15 comments

Comments

@alexdima
Copy link

alexdima commented Sep 14, 2016

  • Electron version: 1.3.6 (win32-ia32 zip)
  • Operating system: Windows 10.0.14393 Build 14393

Create a main.js file that loads a wikipedia page:

var electron = require('electron')
var app = electron.app
var BrowserWindow = electron.BrowserWindow

var mainWindow;

function createWindow () {
    mainWindow = new BrowserWindow({width: 800, height: 600})
    mainWindow.loadURL(`https://en.wikipedia.org/wiki/Timi%C8%99oara`)
    mainWindow.webContents.openDevTools()
    mainWindow.on('closed', function () {
        mainWindow = null
    })
}

app.on('ready', createWindow)

app.on('window-all-closed', function () {
    if (process.platform !== 'darwin') {
        app.quit()
    }
})

app.on('activate', function () {
    if (mainWindow === null) {
        createWindow()
    }
})
  • run electron.exe main.js
  • scroll up and down rapidly by dragging the scrollbar
  • watch CPU usage or Record with the Timeline
  • renderer process goes to 100% CPU usage
  • main process stays at 1-2% CPU usage
  • both Chromium on a version before and Chrome on a version after do not have this problem.

Electron v1.3.6 (process.versions.chrome = 52.0.2743.82)

image

Chromium version 52.0.2743.0

image

Chrome version 53.0.2785.113 m (64-bit)

image

Electron version 0.37.6 (process.versions.chrome = 49.0.2623.75)

image

More info:

  • Reproduces in Electron version 0.37.6
  • fyi @bbondy Reproduces in Brave 0.12.0 (Electron 1.3.10, libchromiumcontent: 52.0.2743.116)
  • Reproduces on Surface Book, in external monitor at 100% zoom
  • Does not reproduce on Lenovo w530 with the same Windows version
  • this impacts VSCode considerably (why I started looking into this)

Hints:

  • The only hints I could find are when clicking on the input events and most of them in Electron appear to be waiting a long time on the main process. For Chromium 52 or Chrome 53, the total time of these events is never longer than 20-30ms, typically even shorter:

image

fyi @zcbenz @kevinsawicki @bpasero @dbaeumer

@alexdima
Copy link
Author

Electron 0.36.11 (process.versions.chrome = 47.0.2526.110)

Does not reproduce
image

I can try "bisecting" releases to see where the issue could have been introduced

@alexdima
Copy link
Author

  • Does not reproduce in Electron v 0.37.0
  • Does not reproduce in Electron v 0.37.3
  • Does not reproduce in Electron b 0.37.5
  • Reproduces every second try in Electron b 0.37.6 (maybe after hovering over a link in the page?)

@alexdima
Copy link
Author

alexdima commented Sep 14, 2016

Another interesting fact:

When using Electron v 1.3.6 and adding the following at the bottom of main.js:

setInterval(function() {
  console.log(  app.isAccessibilitySupportEnabled() );
}, 500);

I get true, so I think Electron believes a screen reader is attached, when in fact it is not.

@alexdima
Copy link
Author

ping @paulcbetts

It looks like on my machine Electron immediately (on startup) goes into accessibility mode.

Possible commit that first appeared in 0.37.6: c474ad0

@alexdima
Copy link
Author

Better repro steps - main.js

var electron = require('electron')
var app = electron.app
var BrowserWindow = electron.BrowserWindow

var mainWindow;

function createWindow () {
    mainWindow = new BrowserWindow({width: 800, height: 900, webPreferences: {backgroundThrottling: false}})
    mainWindow.loadURL(`chrome://accessibility`)
    mainWindow.on('closed', function () {
        mainWindow = null
    })
}

app.on('ready', createWindow)

app.on('window-all-closed', function () {
    if (process.platform !== 'darwin') {
        app.quit()
    }
})

app.on('activate', function () {
    if (mainWindow === null) {
        createWindow()
    }
})

0.37.6

image

0.37.5

image

@alexdima
Copy link
Author

alexdima commented Sep 14, 2016

fyi found this comment in some unrelated project. I have a touch monitor.

http://hg.openjdk.java.net/openjfx/8u/rt/rev/059dc6af444f

 /* By default JAWS does not send WM_GETOBJECT with UiaRootObjectId till
+         * a focus event is raised by UiaRaiseAutomationEvent().
+         * In some systems (i.e. touch monitors), OBJID_CLIENT are sent when
+         * no screen reader is active. Test for SPI_GETSCREENREADER and
+         * UiaClientsAreListening() to avoid initializing accessibility
+         * unnecessarily.
+         */

@alexdima alexdima changed the title 100% CPU usage in "Other" Electron enables accessibility mode all the time with touch monitors (100% CPU usage in "Other") Sep 14, 2016
@anaisbetts
Copy link
Contributor

@alexandrudima Seems legit, send in a PR

@bpasero
Copy link
Contributor

bpasero commented Sep 14, 2016

This seems to be what @paulcbetts reported with #4001 a while ago.

@alexdima
Copy link
Author

Some more information -- https://cs.chromium.org/chromium/src/content/browser/accessibility/browser_accessibility_state_impl.h?q=WM_GETOBJECT&sq=package:chromium&dr=C&l=25

// Screen Reader Detection
// (1) On windows many screen reader detection mechinisms will give false
// positives like relying on the SPI_GETSCREENREADER system parameter. In Chrome
// we attempt to dynamically detect a MSAA client screen reader by calling
// NotifiyWinEvent in NativeWidgetWin with a custom ID and wait to see if the ID
// is requested by a subsequent call to WM_GETOBJECT.

@dbaeumer
Copy link

@alexandrudima impressive analysis of the problem!

@alexdima
Copy link
Author

@paulcbetts Is LegacyRenderWidgetHostHWND something that makes its way down to Electron? i.e. something that is running?

Because it seems to do what Electron wants to do with WM_GETOBJECT:

I don't fully understand what parts of Chromium make it into Electron and how all the switches are set at runtime. Is there a way to find out if this class is alive/running in Electron without needing to compile Chromium locally?

Should Electron do something similar (i.e. NotifyWinEvent in the initialization phase and then fish for a WM_GETOBJECT referencing the same value) or is Chromium already doing that?

From my compiling Electron locally I can cofirm it receives quite some number (10 maybe) of WM_GETOBJECT of OBJID_CLIENT on my machine even when there is no screen reader running. It then receives hundreds of these objects when I start NVDA, for example. Is one option to use the counts as an heuristic for when to call OnScreenReaderDetected?

@zcbenz @paulcbetts I really do not like slowness and would like with your help to put together a PR, but in what direction should I go? I would also be very happy if you are able to fix this. Should I "duplicate" the smarts of LegacyRenderWidgetHostHWND inside Electron?

@anaisbetts
Copy link
Contributor

anaisbetts commented Sep 15, 2016

@alexandrudima Anything under the content component ends up in Electron as part of libchromiumcontent. This usually excludes the "browser" folder, but content/browser is included.

tbh, I'd try the Java approach first, it seems far easier than dealing with this WM_GETOBJECT canary - maybe it Just Works?

@miniak
Copy link
Contributor

miniak commented Oct 13, 2016

I've tried this:

-      if (obj_id == OBJID_CLIENT) {
+      if (obj_id == OBJID_CLIENT && IsScreenReaderActive()) {

with a detection code like this:

static bool IsLibraryLoaded(const wchar_t* name) {
  HMODULE hModule = nullptr;
  return ::GetModuleHandleEx(GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT, name, &hModule) && hModule;
}

static bool IsScreenReaderActive() {
  // BOOL value = false;
  // if (::SystemParametersInfo(SPI_GETSCREENREADER, 0, &value, false) && value)
  //   return true;

  base::win::ScopedHandle hMutex(
    ::CreateMutex(nullptr, false, L"NarratorRunning"));
  if (::GetLastError() == ERROR_ALREADY_EXISTS) return true;

  static const wchar_t* names[] = {
    // NVDA
    L"nvdaHelperRemote.dll",
    // JAWS
    L"jhook.dll"
  };

  for (auto name : names) {
    if (IsLibraryLoaded(name)) return true;
  }

  return false;
}

Seems to work fine with Narrator, JAWS and NVDA for which it has detection. However, it won't work with other accessibility tools.

@miniak
Copy link
Contributor

miniak commented Oct 13, 2016

I am also going the try the Java approach.

@miniak
Copy link
Contributor

miniak commented Oct 13, 2016

Btw I am also experiencing this issue in Windows 10 running in Parallels on Mac

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

6 participants