Description
Continuation of #146683
Current status of investigation
The upstream Electron issue is at electron/electron#30760.
The issue was introduced in Electron v14.0.0-beta.5 and the commit that introduced the issue is electron/electron@172ac25.
The PR electron/electron#35189 fixes the issue where frameless resizable windows show Windows 7 frames during startup, but there's a larger issue that is still unresolved even with that PR. I also searched through the thousands of Chromium commits associated with that Electron commit to see whether they had anything to do with the issue, but I didn't find anything relevant even after reverting several suspicious commits.Minimal repro
const { app, BrowserWindow } = require('electron') function createWindow() { new BrowserWindow({ width: 800, height: 600, frame: false }) } app.whenReady().then(() => { createWindow() }) app.on('window-all-closed', function () { app.quit() })Running
<path-to-electron-exe> <path-to-js-file-above>
with Electron v14.0.0-beta.5 or newer on Windows 10 reproduces the issue.Going through the Chromium code with the PR fix for frameless resizable windows
The PR electron/electron#35189 fixes the issue for frameless resizable windows by calling
set_can_resize()
at an earlier stage of program initialization. Assume the window is frameless and resizable. Then, the program setscan_resize
totrue
earlier in widget_delegate.cc, and the following occurs:
- widget_hwnd_utils.cc:68 is not run, meaning
WS_THICKFRAME
is not removed from the style set in Line 62. This style parameter is passed in to aCreateWindowEx
call at window_impl.cc:213.- hwnd_message_handler.cc:1029 is not run. Instead, the
if
block above is run, which adds aWS_THICKFRAME
style to the style passed into theSetWindowLong
call.- Somehow, because of these
WS_THICKFRAME
styles being passed in, after the window is shown, we get a singleWM_DWMNCRENDERINGCHANGED
message, and noWM_NCPAINT
messages after. NC in this case refers to non-client area, which is the frame. DWM stands for desktop windows manager.After adding a long
setTimeout
of 30 seconds to thecreateWindow
call above, I was able to use Spy++ to log messages to that window and see the following:<000157> 0000000000170B9C R WM_WINDOWPOSCHANGING <000158> 0000000000170B9C S WM_GETICON nType:ICON_BIG <000159> 0000000000170B9C R WM_GETICON hicon:00000000 <000160> 0000000000170B9C P WM_DWMNCRENDERINGCHANGED fRenderingEnabled:True <000161> 0000000000170B9C S WM_GETICON nType:ICON_SMALL2 <000162> 0000000000170B9C R WM_GETICON hicon:00000000 <000163> 0000000000170B9C P message:0x0400 [User-defined:WM_USER+0] wParam:00000003 lParam:00000000 <000164> 0000000000170B9C S WM_GETICON nType:ICON_SMALL <000165> 0000000000170B9C R WM_GETICON hicon:00000000 <000166> 0000000000170B9C P message:0xC112 [Registered:"TaskbarButtonCreated"] wParam:00000000 lParam:00000000 <000167> 0000000000170B9C S WM_GETMINMAXINFO lpmmi:0000002354FFE8F0 <000168> 0000000000170B9C R WM_GETMINMAXINFO lpmmi:0000002354FFE8F0 <000169> 0000000000170B9C P WM_PAINT hdc:00000000
I have also attached full logs to the end of this comment.
The PR doesn't fix the case of frameless non-resizable windows
For frameless windows that cannot resize, even if we call
set_can_resize()
earlier, because the program would pass infalse
, for theCreateWindowEx
andSetWindowLong
calls above, theWS_THICKFRAME
style would not be in the styles sent to the calls. As a result, what I see in Spy++ isWM_DWMNCRENDERINGCHANGED
being toggled three times, and severalWM_WINDOWPOSCHANGING
calls that result inWM_NCPAINT
messages being emitted. Using Spy++, I confirmed that theWM_WINDOWPOSCHANGING
calls have the flagSWP_FRAMECHANGED
set.<000174> 0000000000130AAA P WM_DWMNCRENDERINGCHANGED fRenderingEnabled:True <000175> 0000000000130AAA S WM_GETICON nType:ICON_SMALL2 <000176> 0000000000130AAA R WM_GETICON hicon:00000000 <000177> 0000000000130AAA P WM_DWMNCRENDERINGCHANGED fRenderingEnabled:False <000178> 0000000000130AAA S WM_WINDOWPOSCHANGING lpwp:000000E709BFE280 <000179> 0000000000130AAA R WM_WINDOWPOSCHANGING <000180> 0000000000130AAA S WM_NCCALCSIZE fCalcValidRects:True lpncsp:000000E709BFE250 <000181> 0000000000130AAA R WM_NCCALCSIZE fuValidRect:0000 lpncsp:000000E709BFE250 <000182> 0000000000130AAA S WM_NCPAINT hrgn:00000001 <000183> 0000000000130AAA R WM_NCPAINT <000184> 0000000000130AAA S WM_ERASEBKGND hdc:5B01182B <000185> 0000000000130AAA R WM_ERASEBKGND fErased:True <000186> 0000000000130AAA S WM_WINDOWPOSCHANGED lpwp:000000E709BFE280 <000187> 0000000000130AAA S WM_WINDOWPOSCHANGING lpwp:000000E709BFDB20 <000188> 0000000000130AAA R WM_WINDOWPOSCHANGING <000189> 0000000000130AAA S WM_NCCALCSIZE fCalcValidRects:True lpncsp:000000E709BFDAF0 <000190> 0000000000130AAA R WM_NCCALCSIZE fuValidRect:0000 lpncsp:000000E709BFDAF0 <000191> 0000000000130AAA S WM_NCPAINT hrgn:00000001 <000192> 0000000000130AAA R WM_NCPAINT <000193> 0000000000130AAA S WM_ERASEBKGND hdc:FFFFFFFF87011810 <000194> 0000000000130AAA R WM_ERASEBKGND fErased:True <000195> 0000000000130AAA S WM_WINDOWPOSCHANGED lpwp:000000E709BFDB20 <000196> 0000000000130AAA R WM_WINDOWPOSCHANGED <000197> 0000000000130AAA R WM_WINDOWPOSCHANGED <000198> 0000000000130AAA S WM_GETICON nType:ICON_SMALL <000199> 0000000000130AAA R WM_GETICON hicon:00000000 <000200> 0000000000130AAA P message:0x0400 [User-defined:WM_USER+0] wParam:00000003 lParam:00000000 <000201> 0000000000130AAA P WM_DWMNCRENDERINGCHANGED fRenderingEnabled:True <000202> 0000000000130AAA S WM_WINDOWPOSCHANGING lpwp:000000E709BFE280 <000203> 0000000000130AAA R WM_WINDOWPOSCHANGING <000204> 0000000000130AAA S WM_NCCALCSIZE fCalcValidRects:True lpncsp:000000E709BFE250 <000205> 0000000000130AAA R WM_NCCALCSIZE fuValidRect:0000 lpncsp:000000E709BFE250 <000206> 0000000000130AAA S WM_NCPAINT hrgn:00000001 <000207> 0000000000130AAA R WM_NCPAINT <000208> 0000000000130AAA S WM_ERASEBKGND hdc:010118D7 <000209> 0000000000130AAA R WM_ERASEBKGND fErased:True <000210> 0000000000130AAA S WM_WINDOWPOSCHANGED lpwp:000000E709BFE280 <000211> 0000000000130AAA S WM_WINDOWPOSCHANGING lpwp:000000E709BFDB20 <000212> 0000000000130AAA R WM_WINDOWPOSCHANGING <000213> 0000000000130AAA S WM_NCCALCSIZE fCalcValidRects:True lpncsp:000000E709BFDAF0 <000214> 0000000000130AAA R WM_NCCALCSIZE fuValidRect:0000 lpncsp:000000E709BFDAF0 <000215> 0000000000130AAA S WM_NCPAINT hrgn:00000001 <000216> 0000000000130AAA R WM_NCPAINT <000217> 0000000000130AAA S WM_ERASEBKGND hdc:5B01182B <000218> 0000000000130AAA R WM_ERASEBKGND fErased:True <000219> 0000000000130AAA S WM_WINDOWPOSCHANGED lpwp:000000E709BFDB20 <000220> 0000000000130AAA R WM_WINDOWPOSCHANGED <000221> 0000000000130AAA R WM_WINDOWPOSCHANGED
More information
I have searched the Chromium and Electron codebases for
WS_THICKFRAME
andSWP_FRAMECHANGED
(as well asSWP_DRAWFRAME
) to see why the frame might be rendering more often whenWS_THICKFRAME
is not passed in. However, I haven't found anything within the codebase itself.Searching Windows documentation, I found the following:
WS_THICKFRAME
causesWM_GETMINMAXINFO
to be sent. Reference: https://docs.microsoft.com/en-us/windows/win32/winmsg/wm-windowposchanging. However, I'm not sure whether this could explain the difference in behaviour between resizable and non-resizable windows.SetWindowPos
can pass inSWP_FRAMECHANGED
orSWP_DRAWFRAME
to sendNC_CALCSIZE
to the window, which is why I searched for those two constants within the codebase. Reference: https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-setwindowpos. Also, Chromium handlesNC_CALCSIZE
by returningWVR_REDRAW
at hwnd_message_handler.cc:2353, which I assume causes theWM_NCPAINT
message to occur.Also, Electron calls
SetWindowLong
at native_window_views.cc:350. I noticed that if I don't callSetWindowLong
there, the Windows 7 frame doesn't show up, but then the Electron window doesn't render with the proper style (see electron/electron#32981). I also noticed that when the Windows 7 frame shows up, it has the style of whatever was passed into that specificSetWindowLong
call. For example, if I addWS_SYSMENU
to the call, then for a second I see a Windows 7 frame withWS_SYSMENU
applied.I also tried creating a minimal project in VS to reproduce the issue but wasn't successful. The frame that rendered seemed to have a Windows 10 style rather than a Windows 7 one.
I also tried registering a listener in
hwnd_message_handler.cc
for theWM_DWMNCRENDERINGCHANGED
messages, and it did seem to get hit, though I couldn't do anything with it. There's also the mysterious ScopedRedrawLock, but I wasn't able to apply it to resolve the issue.Attached log files
A text file showing the good case where WM_DWMNCRENDERINGCHANGED is only called once. The window is frameless and resizable.
A text file showing the bad case where WM_DWMNCRENDERINGCHANGED is called three times. The window is frameless and not resizable.