Skip to content

Investigate WS_THICKFRAME impact when creating frameless windows #158065

Closed
@deepak1556

Description

@deepak1556

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 sets can_resize to true earlier in widget_delegate.cc, and the following occurs:

  1. 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 a CreateWindowEx call at window_impl.cc:213.
  2. hwnd_message_handler.cc:1029 is not run. Instead, the if block above is run, which adds a WS_THICKFRAME style to the style passed into the SetWindowLong call.
  3. Somehow, because of these WS_THICKFRAME styles being passed in, after the window is shown, we get a single WM_DWMNCRENDERINGCHANGED message, and no WM_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 the createWindow 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 in false, for the CreateWindowEx and SetWindowLong calls above, the WS_THICKFRAME style would not be in the styles sent to the calls. As a result, what I see in Spy++ is WM_DWMNCRENDERINGCHANGED being toggled three times, and several WM_WINDOWPOSCHANGING calls that result in WM_NCPAINT messages being emitted. Using Spy++, I confirmed that the WM_WINDOWPOSCHANGING calls have the flag SWP_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 and SWP_FRAMECHANGED (as well as SWP_DRAWFRAME) to see why the frame might be rendering more often when WS_THICKFRAME is not passed in. However, I haven't found anything within the codebase itself.

Searching Windows documentation, I found the following:

Also, Electron calls SetWindowLong at native_window_views.cc:350. I noticed that if I don't call SetWindowLong 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 specific SetWindowLong call. For example, if I add WS_SYSMENU to the call, then for a second I see a Windows 7 frame with WS_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 the WM_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.

Metadata

Metadata

Labels

debtCode quality issueselectronIssues and items related to ElectronupstreamIssue identified as 'upstream' component related (exists outside of VS Code)upstream-issue-linkedThis is an upstream issue that has been reported upstreamwindowsVS Code on Windows issuesworkbench-os-integrationNative OS integration issues

Type

No type

Projects

No projects

Milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions