mirror of
https://github.com/microsoft/PowerToys.git
synced 2025-12-16 03:37:59 +01:00
<!-- Enter a brief description/summary of your PR here. What does it
fix/what does it change/how was it tested (even manually, if necessary)?
-->
## Summary of the Pull Request
This PR aims to fix some positioning issues of the form used as preview
handler.
It fixes the following issues:
1. The floating window, detached from Explorer that sometimes appears:
#33491 #27475 #24985
2. The **CoreWebView2 members cannot be accessed after the WebView2
control is disposed** crash: #27276
3. `PowerToys.*.PreviewHandler.exe` process leak
### Repro steps for issue 1
- Navigate through files in a folder invoking their preview handler
- Minimize/Restore Explorer quickly (spam WIN+D usually works)
- 2 weird issues happen:
- Some `PowerToys.*.PreviewHandler.exe` processes are leaked
- Some `PowerToys.*.PreviewHandler.exe` are started with a `NULL` `HWND`

This happens because
[IPreviewHandler::DoPreview](https://learn.microsoft.com/windows/win32/api/shobjidl_core/nf-shobjidl_core-ipreviewhandler-dopreview)
is called multiple times and sometimes before calling
[IPreviewHandler::SetWindow](https://learn.microsoft.com/windows/win32/api/shobjidl_core/nf-shobjidl_core-ipreviewhandler-setwindow).
When the managed previewer try to set the parent of the form to the
`NULL` `HWND`, the desktop window is used instead, resulting in the
floating preview window being displayed.
Reference:
https://learn.microsoft.com/windows/win32/api/winuser/nf-winuser-setparent#parameters
5d77874382/src/modules/previewpane/common/controls/FormHandlerControl.cs (L136)
### Repro steps for issue 2
- Preview a file
- Restart `explorer.exe` process
- Make sure `PowerToys.*.PreviewHandler.exe` is leaked and still running
- Preview the same file again
- Preview is displayed (another process is launched)
- Minimize Explorer
What happens here is that the form of the old process have an invalid
`HWND` as parent but receive the `SetRect` for some reason.
<!-- Please review the items on the PR checklist before submitting-->
## PR Checklist
- [x] **Closes:** #33491 #27475 #24985 #27276
- [ ] **Communication:** I've discussed this with core contributors
already. If work hasn't been agreed, this work might be rejected
- [ ] **Tests:** Added/updated and all pass
- [ ] **Localization:** All end user facing strings can be localized
- [ ] **Dev docs:** Added/updated
- [ ] **New binaries:** Added on the required places
- [ ] [JSON for
signing](https://github.com/microsoft/PowerToys/blob/main/.pipelines/ESRPSigning_core.json)
for new binaries
- [ ] [WXS for
installer](https://github.com/microsoft/PowerToys/blob/main/installer/PowerToysSetup/Product.wxs)
for new binaries and localization folder
- [ ] [YML for CI
pipeline](https://github.com/microsoft/PowerToys/blob/main/.pipelines/ci/templates/build-powertoys-steps.yml)
for new test projects
- [ ] [YML for signed
pipeline](https://github.com/microsoft/PowerToys/blob/main/.pipelines/release.yml)
- [ ] **Documentation updated:** If checked, please file a pull request
on [our docs
repo](https://github.com/MicrosoftDocs/windows-uwp/tree/docs/hub/powertoys)
and link it here: #xxx
<!-- Provide a more detailed description of the PR, other things fixed
or any additional comments/features here -->
## Detailed Description of the Pull Request / Additional comments
- Don't start preview pane process when `HWND` is `NULL`
- Terminate the preview pane process when setting parent fails
- Prevent leaking processes closing them when a new preview is requested
- Fixed an issue where PDF and SVG previews weren't updated after
restoring Explorer
- Added some error handling in the `UpdateWindowBounds` method of the
managed preview
- Terminate the preview pane when the `SetRect` event is received but
the parent `HWND` has become invalid
<!-- Describe how you validated the behavior. Add automated tests
wherever possible, but list manual validation steps taken as well -->
## Validation Steps Performed
- Manually tested all preview panes also using multiple Explorer windows
- Validated that when Explorer is minimized/restored the preview is
updated
- Tested the preview pane resize
- Validated that no window, no taskbar icon and no errors appear on both
repro steps
163 lines
5.4 KiB
C#
163 lines
5.4 KiB
C#
// Copyright (c) Microsoft Corporation
|
|
// The Microsoft Corporation licenses this file to you under the MIT license.
|
|
// See the LICENSE file in the project root for more information.
|
|
|
|
using System;
|
|
using System.Drawing;
|
|
using System.Windows.Forms;
|
|
using Common.ComInterlop;
|
|
using PreviewHandlerCommon.ComInterop;
|
|
|
|
namespace Common
|
|
{
|
|
/// <summary>
|
|
/// Form based implementation of <see cref="IPreviewHandlerControl"/>.
|
|
/// </summary>
|
|
public abstract class FormHandlerControl : Form, IPreviewHandlerControl
|
|
{
|
|
/// <summary>
|
|
/// Needed to make the form a child window.
|
|
/// </summary>
|
|
private static int gwlStyle = -16;
|
|
private static int wsChild = 0x40000000;
|
|
|
|
/// <summary>
|
|
/// Holds the parent window handle.
|
|
/// </summary>
|
|
private IntPtr parentHwnd;
|
|
|
|
/// <summary>
|
|
/// Initializes a new instance of the <see cref="FormHandlerControl"/> class.
|
|
/// </summary>
|
|
public FormHandlerControl()
|
|
{
|
|
// Gets the handle of the control to create the control on the VI thread. Invoking the Control.Handle get accessor forces the creation of the underlying window for the control.
|
|
// This is important, because the thread that instantiates the preview handler component and calls its constructor is a single-threaded apartment (STA) thread, but the thread that calls into the interface members later on is a multithreaded apartment (MTA) thread. Windows Forms controls are meant to run on STA threads.
|
|
// More details: https://learn.microsoft.com/archive/msdn-magazine/2007/january/windows-vista-and-office-writing-your-own-preview-handlers.
|
|
var forceCreation = this.Handle;
|
|
|
|
this.FormBorderStyle = FormBorderStyle.None;
|
|
this.Visible = false;
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
public IntPtr GetWindowHandle()
|
|
{
|
|
return this.Handle;
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
public void QueryFocus(out IntPtr result)
|
|
{
|
|
var getResult = IntPtr.Zero;
|
|
getResult = NativeMethods.GetFocus();
|
|
result = getResult;
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
public void SetBackgroundColor(Color argbColor)
|
|
{
|
|
this.BackColor = argbColor;
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
public void SetFocus()
|
|
{
|
|
this.Focus();
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
public void SetFont(Font font)
|
|
{
|
|
this.Font = font;
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
public bool SetRect(Rectangle windowBounds)
|
|
{
|
|
return this.UpdateWindowBounds(parentHwnd, windowBounds);
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
public void SetTextColor(Color color)
|
|
{
|
|
this.ForeColor = color;
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
public bool SetWindow(IntPtr hwnd, Rectangle rect)
|
|
{
|
|
this.parentHwnd = hwnd;
|
|
return this.UpdateWindowBounds(hwnd, rect);
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
public virtual void Unload()
|
|
{
|
|
this.Visible = false;
|
|
foreach (Control c in this.Controls)
|
|
{
|
|
c.Dispose();
|
|
}
|
|
|
|
this.Controls.Clear();
|
|
|
|
// Call garbage collection at the time of unloading of Preview.
|
|
// Which is preventing prevhost.exe to exit at the time of closing File explorer.
|
|
// Preview Handlers run in a separate process from PowerToys. This will not affect the performance of other modules.
|
|
// Mitigate the following GitHub issue: https://github.com/microsoft/PowerToys/issues/1468
|
|
GC.Collect();
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
public virtual void DoPreview<T>(T dataSource)
|
|
{
|
|
this.Visible = true;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Update the Form Control window with the passed rectangle.
|
|
/// </summary>
|
|
public bool UpdateWindowBounds(IntPtr hwnd, Rectangle newBounds)
|
|
{
|
|
if (hwnd == IntPtr.Zero || !NativeMethods.IsWindow(hwnd))
|
|
{
|
|
// If the HWND is IntPtr.Zero the desktop window will be used as parent.
|
|
return false;
|
|
}
|
|
|
|
if (this.Disposing || this.IsDisposed)
|
|
{
|
|
// For unclear reasons, this can be called when handling an error and the form has already been disposed.
|
|
return false;
|
|
}
|
|
|
|
// We must set the WS_CHILD style to change the form to a control within the Explorer preview pane
|
|
int windowStyle = NativeMethods.GetWindowLong(Handle, gwlStyle);
|
|
if ((windowStyle & wsChild) == 0)
|
|
{
|
|
_ = NativeMethods.SetWindowLong(Handle, gwlStyle, windowStyle | wsChild);
|
|
}
|
|
|
|
if (NativeMethods.SetParent(Handle, hwnd) == IntPtr.Zero)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (newBounds.IsEmpty)
|
|
{
|
|
RECT s = default(RECT);
|
|
NativeMethods.GetClientRect(hwnd, ref s);
|
|
newBounds = new Rectangle(s.Left, s.Top, s.Right - s.Left, s.Bottom - s.Top);
|
|
}
|
|
|
|
if (Bounds.Right != newBounds.Right || Bounds.Bottom != newBounds.Bottom || Bounds.Left != newBounds.Left || Bounds.Top != newBounds.Top)
|
|
{
|
|
Bounds = newBounds;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
}
|
|
}
|