Nullify widget state on NativeWidget classes post widget destruction
This CL introduces a new ClientDestroyedWidget() method on NativeWidgetPrivate. This the associated NativeWidget to clear any Widget-associated state and avoid dangling pointers. This is specifically necessary under the CLIENT_OWNS_WIDGET ownership model where the NativeWidget (managed by the OS) can outlive the Widget. This patch leverages this method to clear widget-associated state on - DesktopBrowserFrameAura - BrowserDesktopWindowTreeHostWin - BrowserDesktopWindowTreeHostLinux A follow up will leverage this new method to additionally clear such state from Mac NativeWidget classes. Bug: 413168662, 413385730 Change-Id: Ie3825b624cf2853865f3f501e673d3ddcf24aa5b Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/6497480 Reviewed-by: Darryl James <dljames@chromium.org> Reviewed-by: Keren Zhu <kerenzhu@chromium.org> Commit-Queue: Tom Lukaszewicz <tluk@chromium.org> Cr-Commit-Position: refs/heads/main@{#1454902}
This commit is contained in:

committed by
Chromium LUCI CQ

parent
041f13fac2
commit
a53f684314
@ -34,6 +34,10 @@ class BrowserDesktopWindowTreeHost {
|
||||
// Returns true if the OS takes care of showing the system menu. Returning
|
||||
// false means BrowserFrame handles showing the system menu.
|
||||
virtual bool UsesNativeSystemMenu() const = 0;
|
||||
|
||||
// A lifecycle hook invoked when the views::Widget associated with this window
|
||||
// tree host was destroyed by the client.
|
||||
virtual void ClientDestroyedWidget() = 0;
|
||||
};
|
||||
|
||||
#endif // CHROME_BROWSER_UI_VIEWS_FRAME_BROWSER_DESKTOP_WINDOW_TREE_HOST_H_
|
||||
|
@ -108,7 +108,6 @@ void BrowserDesktopWindowTreeHostLinux::AddAdditionalInitProperties(
|
||||
ui::PlatformWindowInitProperties* properties) {
|
||||
views::DesktopWindowTreeHostLinux::AddAdditionalInitProperties(params,
|
||||
properties);
|
||||
|
||||
auto* profile = browser_view_->browser()->profile();
|
||||
const auto* linux_ui_theme = ui::LinuxUiTheme::GetForProfile(profile);
|
||||
properties->prefer_dark_theme =
|
||||
@ -128,6 +127,14 @@ bool BrowserDesktopWindowTreeHostLinux::UsesNativeSystemMenu() const {
|
||||
return false;
|
||||
}
|
||||
|
||||
void BrowserDesktopWindowTreeHostLinux::ClientDestroyedWidget() {
|
||||
#if BUILDFLAG(USE_DBUS)
|
||||
dbus_appmenu_.reset();
|
||||
#endif
|
||||
browser_frame_ = nullptr;
|
||||
browser_view_ = nullptr;
|
||||
}
|
||||
|
||||
void BrowserDesktopWindowTreeHostLinux::FrameTypeChanged() {
|
||||
DesktopWindowTreeHostPlatform::FrameTypeChanged();
|
||||
UpdateFrameHints();
|
||||
@ -164,6 +171,8 @@ void BrowserDesktopWindowTreeHostLinux::UnlockMouse(aura::Window* window) {
|
||||
|
||||
void BrowserDesktopWindowTreeHostLinux::TabDraggingKindChanged(
|
||||
TabDragKind tab_drag_kind) {
|
||||
CHECK(browser_frame_);
|
||||
CHECK(browser_view_);
|
||||
// If there's no tabs left, the browser window is about to close, so don't
|
||||
// call SetOverrideRedirect() to prevent the window from flashing.
|
||||
if (!browser_view_->tabstrip()->GetModelCount()) {
|
||||
@ -200,6 +209,10 @@ bool BrowserDesktopWindowTreeHostLinux::SupportsClientFrameShadow() const {
|
||||
}
|
||||
|
||||
void BrowserDesktopWindowTreeHostLinux::UpdateFrameHints() {
|
||||
if (!browser_frame_) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto* window = platform_window();
|
||||
auto window_state = window->GetPlatformWindowState();
|
||||
float scale = device_scale_factor();
|
||||
@ -320,6 +333,7 @@ void BrowserDesktopWindowTreeHostLinux::CloseNow() {
|
||||
void BrowserDesktopWindowTreeHostLinux::Show(
|
||||
ui::mojom::WindowShowState show_state,
|
||||
const gfx::Rect& restore_bounds) {
|
||||
CHECK(browser_view_);
|
||||
DesktopWindowTreeHostLinux::Show(show_state, restore_bounds);
|
||||
|
||||
const std::string& startup_id =
|
||||
@ -332,14 +346,16 @@ void BrowserDesktopWindowTreeHostLinux::Show(
|
||||
|
||||
bool BrowserDesktopWindowTreeHostLinux::IsOverrideRedirect(
|
||||
const ui::X11Extension& x11_extension) const {
|
||||
return (browser_frame_->tab_drag_kind() == TabDragKind::kAllTabs) &&
|
||||
return (browser_frame_ &&
|
||||
browser_frame_->tab_drag_kind() == TabDragKind::kAllTabs) &&
|
||||
x11_extension.IsWmTiling() && x11_extension.CanResetOverrideRedirect();
|
||||
}
|
||||
|
||||
gfx::Insets BrowserDesktopWindowTreeHostLinux::CalculateInsetsInDIP(
|
||||
ui::PlatformWindowState window_state) const {
|
||||
// If we are not showing frame, the insets should be zero.
|
||||
if (!IsShowingFrame(browser_frame_->native_browser_frame()->UseCustomFrame(),
|
||||
if (!browser_frame_ ||
|
||||
!IsShowingFrame(browser_frame_->native_browser_frame()->UseCustomFrame(),
|
||||
window_state)) {
|
||||
return gfx::Insets();
|
||||
}
|
||||
@ -351,6 +367,10 @@ gfx::Insets BrowserDesktopWindowTreeHostLinux::CalculateInsetsInDIP(
|
||||
void BrowserDesktopWindowTreeHostLinux::OnWindowStateChanged(
|
||||
ui::PlatformWindowState old_state,
|
||||
ui::PlatformWindowState new_state) {
|
||||
if (!browser_view_) {
|
||||
return;
|
||||
}
|
||||
|
||||
DesktopWindowTreeHostLinux::OnWindowStateChanged(old_state, new_state);
|
||||
|
||||
bool fullscreen_changed = new_state == ui::PlatformWindowState::kFullScreen ||
|
||||
@ -373,6 +393,10 @@ void BrowserDesktopWindowTreeHostLinux::OnWindowStateChanged(
|
||||
|
||||
void BrowserDesktopWindowTreeHostLinux::OnWindowTiledStateChanged(
|
||||
ui::WindowTiledEdges new_tiled_edges) {
|
||||
if (!browser_frame_) {
|
||||
return;
|
||||
}
|
||||
|
||||
bool maximized = new_tiled_edges.top && new_tiled_edges.left &&
|
||||
new_tiled_edges.bottom && new_tiled_edges.right;
|
||||
bool tiled = new_tiled_edges.top || new_tiled_edges.left ||
|
||||
|
@ -71,6 +71,7 @@ class BrowserDesktopWindowTreeHostLinux
|
||||
DesktopWindowTreeHost* AsDesktopWindowTreeHost() override;
|
||||
int GetMinimizeButtonOffset() const override;
|
||||
bool UsesNativeSystemMenu() const override;
|
||||
void ClientDestroyedWidget() override;
|
||||
|
||||
// BrowserWindowTreeHostPlatform:
|
||||
void FrameTypeChanged() override;
|
||||
|
@ -241,6 +241,7 @@ BrowserDesktopWindowTreeHostWin::~BrowserDesktopWindowTreeHostWin() = default;
|
||||
|
||||
views::NativeMenuWin* BrowserDesktopWindowTreeHostWin::GetSystemMenu() {
|
||||
if (!system_menu_.get()) {
|
||||
CHECK(browser_frame_);
|
||||
SystemMenuInsertionDelegateWin insertion_delegate;
|
||||
system_menu_ = std::make_unique<views::NativeMenuWin>(
|
||||
browser_frame_->GetSystemMenuModel(), GetHWND());
|
||||
@ -265,6 +266,13 @@ bool BrowserDesktopWindowTreeHostWin::UsesNativeSystemMenu() const {
|
||||
return true;
|
||||
}
|
||||
|
||||
void BrowserDesktopWindowTreeHostWin::ClientDestroyedWidget() {
|
||||
system_menu_.reset();
|
||||
browser_window_property_manager_.reset();
|
||||
browser_frame_ = nullptr;
|
||||
browser_view_ = nullptr;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// BrowserDesktopWindowTreeHostWin, views::DesktopWindowTreeHostWin overrides:
|
||||
|
||||
@ -298,7 +306,9 @@ void BrowserDesktopWindowTreeHostWin::HandleWindowMinimizedOrRestored(
|
||||
bool restored) {
|
||||
DesktopWindowTreeHostWin::HandleWindowMinimizedOrRestored(restored);
|
||||
|
||||
browser_view_->UpdateLoadingAnimations(restored);
|
||||
if (browser_view_) {
|
||||
browser_view_->UpdateLoadingAnimations(restored);
|
||||
}
|
||||
}
|
||||
|
||||
void BrowserDesktopWindowTreeHostWin::HandleRequestClose() {
|
||||
@ -328,7 +338,7 @@ bool BrowserDesktopWindowTreeHostWin::GetClientAreaInsets(
|
||||
gfx::Insets* insets,
|
||||
int frame_thickness) const {
|
||||
// Always use default insets for opaque frame.
|
||||
if (!ShouldUseNativeFrame()) {
|
||||
if (!browser_view_ || !ShouldUseNativeFrame()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -373,7 +383,8 @@ bool BrowserDesktopWindowTreeHostWin::GetDwmFrameInsetsInPixels(
|
||||
// an opaque frame, leading to graphical glitches behind the opaque frame.
|
||||
// Instead, we use that function below to tell us whether the frame is
|
||||
// currently native or opaque.
|
||||
if (!GetWidget()->client_view() || !browser_view_->GetIsNormalType() ||
|
||||
if (!browser_view_ || !browser_frame_ || !GetWidget()->client_view() ||
|
||||
!browser_view_->GetIsNormalType() ||
|
||||
!DesktopWindowTreeHostWin::ShouldUseNativeFrame()) {
|
||||
return false;
|
||||
}
|
||||
@ -395,6 +406,10 @@ bool BrowserDesktopWindowTreeHostWin::GetDwmFrameInsetsInPixels(
|
||||
}
|
||||
|
||||
void BrowserDesktopWindowTreeHostWin::HandleCreate() {
|
||||
if (!browser_view_) {
|
||||
return;
|
||||
}
|
||||
|
||||
DesktopWindowTreeHostWin::HandleCreate();
|
||||
browser_window_property_manager_ =
|
||||
BrowserWindowPropertyManager::CreateBrowserWindowPropertyManager(
|
||||
@ -484,6 +499,11 @@ void BrowserDesktopWindowTreeHostWin::PostHandleMSG(UINT message,
|
||||
}
|
||||
|
||||
views::FrameMode BrowserDesktopWindowTreeHostWin::GetFrameMode() const {
|
||||
if (!browser_view_) {
|
||||
// If there is no browser view the frame should be system drawn.
|
||||
return views::FrameMode::SYSTEM_DRAWN;
|
||||
}
|
||||
|
||||
const views::FrameMode system_frame_mode =
|
||||
ShouldBrowserCustomDrawTitlebar(browser_view_)
|
||||
? views::FrameMode::SYSTEM_DRAWN_NO_CONTROLS
|
||||
@ -506,7 +526,8 @@ views::FrameMode BrowserDesktopWindowTreeHostWin::GetFrameMode() const {
|
||||
}
|
||||
|
||||
bool BrowserDesktopWindowTreeHostWin::ShouldUseNativeFrame() const {
|
||||
if (!views::DesktopWindowTreeHostWin::ShouldUseNativeFrame()) {
|
||||
if (!browser_view_ ||
|
||||
!views::DesktopWindowTreeHostWin::ShouldUseNativeFrame()) {
|
||||
return false;
|
||||
}
|
||||
// This function can get called when the Browser window is closed i.e. in the
|
||||
@ -528,6 +549,7 @@ bool BrowserDesktopWindowTreeHostWin::ShouldUseNativeFrame() const {
|
||||
|
||||
bool BrowserDesktopWindowTreeHostWin::ShouldWindowContentsBeTransparent()
|
||||
const {
|
||||
CHECK(browser_view_);
|
||||
return !ShouldBrowserCustomDrawTitlebar(browser_view_) &&
|
||||
views::DesktopWindowTreeHostWin::ShouldWindowContentsBeTransparent();
|
||||
}
|
||||
@ -539,6 +561,7 @@ void BrowserDesktopWindowTreeHostWin::OnProfileAvatarChanged(
|
||||
const base::FilePath& profile_path) {
|
||||
// If we're currently badging the window icon (>1 available profile),
|
||||
// and this window's profile's avatar changed, update the window icon.
|
||||
CHECK(browser_view_);
|
||||
if (browser_view_->browser()->profile()->GetPath() == profile_path &&
|
||||
g_browser_process->profile_manager()
|
||||
->GetProfileAttributesStorage()
|
||||
@ -613,6 +636,7 @@ void BrowserDesktopWindowTreeHostWin::SetWindowIcon(bool badged) {
|
||||
// icon is valid until replaced with the new icon.
|
||||
base::win::ScopedGDIObject<HICON> previous_icon = std::move(icon_handle_);
|
||||
if (badged) {
|
||||
CHECK(browser_view_);
|
||||
icon_handle_ = IconUtil::CreateHICONFromSkBitmap(
|
||||
GetBadgedIconBitmapForProfile(browser_view_->browser()->profile()));
|
||||
} else {
|
||||
|
@ -56,6 +56,7 @@ class BrowserDesktopWindowTreeHostWin
|
||||
DesktopWindowTreeHost* AsDesktopWindowTreeHost() override;
|
||||
int GetMinimizeButtonOffset() const override;
|
||||
bool UsesNativeSystemMenu() const override;
|
||||
void ClientDestroyedWidget() override;
|
||||
|
||||
// Overridden from DesktopWindowTreeHostWin:
|
||||
void Init(const views::Widget::InitParams& params) override;
|
||||
|
@ -31,7 +31,6 @@ DesktopBrowserFrameAura::DesktopBrowserFrameAura(BrowserFrame* browser_frame,
|
||||
browser_view_(browser_view),
|
||||
browser_frame_(browser_frame),
|
||||
browser_desktop_window_tree_host_(nullptr) {
|
||||
widget_observation_.Observe(browser_frame);
|
||||
GetNativeWindow()->SetName("BrowserFrameAura");
|
||||
}
|
||||
|
||||
@ -132,8 +131,8 @@ bool DesktopBrowserFrameAura::ShouldUseInitialVisibleOnAllWorkspaces() const {
|
||||
return true;
|
||||
}
|
||||
|
||||
void DesktopBrowserFrameAura::OnWidgetDestroyed(views::Widget* widget) {
|
||||
void DesktopBrowserFrameAura::ClientDestroyedWidget() {
|
||||
browser_desktop_window_tree_host_->ClientDestroyedWidget();
|
||||
browser_frame_ = nullptr;
|
||||
browser_view_ = nullptr;
|
||||
widget_observation_.Reset();
|
||||
}
|
||||
|
@ -8,13 +8,11 @@
|
||||
#include <memory>
|
||||
|
||||
#include "base/memory/raw_ptr.h"
|
||||
#include "base/scoped_observation.h"
|
||||
#include "chrome/browser/ui/views/frame/native_browser_frame.h"
|
||||
#include "ui/aura/window.h"
|
||||
#include "ui/base/mojom/window_show_state.mojom-forward.h"
|
||||
#include "ui/views/context_menu_controller.h"
|
||||
#include "ui/views/widget/desktop_aura/desktop_native_widget_aura.h"
|
||||
#include "ui/views/widget/widget_observer.h"
|
||||
|
||||
class BrowserDesktopWindowTreeHost;
|
||||
class BrowserFrame;
|
||||
@ -31,8 +29,7 @@ class VisibilityController;
|
||||
// the window frame for the Chrome browser window.
|
||||
//
|
||||
class DesktopBrowserFrameAura : public views::DesktopNativeWidgetAura,
|
||||
public NativeBrowserFrame,
|
||||
public views::WidgetObserver {
|
||||
public NativeBrowserFrame {
|
||||
public:
|
||||
DesktopBrowserFrameAura(BrowserFrame* browser_frame,
|
||||
BrowserView* browser_view);
|
||||
@ -68,9 +65,7 @@ class DesktopBrowserFrameAura : public views::DesktopNativeWidgetAura,
|
||||
bool HandleKeyboardEvent(const input::NativeWebKeyboardEvent& event) override;
|
||||
bool ShouldRestorePreviousBrowserWidgetState() const override;
|
||||
bool ShouldUseInitialVisibleOnAllWorkspaces() const override;
|
||||
|
||||
// views::WidgetObserver:
|
||||
void OnWidgetDestroyed(views::Widget* widget) override;
|
||||
void ClientDestroyedWidget() override;
|
||||
|
||||
private:
|
||||
// The BrowserView is our ClientView. This is a pointer to it.
|
||||
@ -82,9 +77,6 @@ class DesktopBrowserFrameAura : public views::DesktopNativeWidgetAura,
|
||||
browser_desktop_window_tree_host_;
|
||||
|
||||
std::unique_ptr<wm::VisibilityController> visibility_controller_;
|
||||
|
||||
base::ScopedObservation<views::Widget, views::WidgetObserver>
|
||||
widget_observation_{this};
|
||||
};
|
||||
|
||||
#endif // CHROME_BROWSER_UI_VIEWS_FRAME_DESKTOP_BROWSER_FRAME_AURA_H_
|
||||
|
@ -8,10 +8,13 @@ using testing::Return;
|
||||
|
||||
namespace views {
|
||||
|
||||
MockNativeWidget::MockNativeWidget(Widget* widget) : widget_(widget) {}
|
||||
MockNativeWidget::MockNativeWidget(Widget* widget)
|
||||
: widget_(widget->GetWeakPtr()) {}
|
||||
|
||||
MockNativeWidget::~MockNativeWidget() {
|
||||
widget_->OnNativeWidgetDestroyed();
|
||||
if (widget_) {
|
||||
widget_->OnNativeWidgetDestroyed();
|
||||
}
|
||||
}
|
||||
|
||||
base::WeakPtr<internal::NativeWidgetPrivate> MockNativeWidget::GetWeakPtr() {
|
||||
|
@ -46,6 +46,7 @@ class MockNativeWidget : public internal::NativeWidgetPrivate {
|
||||
MOCK_METHOD(const ui::Layer*, GetLayer, (), (const override));
|
||||
MOCK_METHOD(void, ReorderNativeViews, (), (override));
|
||||
MOCK_METHOD(void, ViewRemoved, (View * view), (override));
|
||||
MOCK_METHOD(void, ClientDestroyedWidget, (), (override));
|
||||
MOCK_METHOD(void,
|
||||
SetNativeWindowProperty,
|
||||
(const char* name, void* value),
|
||||
@ -187,7 +188,7 @@ class MockNativeWidget : public internal::NativeWidgetPrivate {
|
||||
base::WeakPtr<NativeWidgetPrivate> GetWeakPtr() override;
|
||||
|
||||
private:
|
||||
raw_ptr<Widget> widget_;
|
||||
base::WeakPtr<Widget> widget_;
|
||||
base::WeakPtrFactory<MockNativeWidget> weak_factory_{this};
|
||||
};
|
||||
|
||||
|
@ -26,6 +26,8 @@ gfx::Rect NativeWidgetPrivate::ConstrainBoundsToDisplayWorkArea(
|
||||
return new_bounds;
|
||||
}
|
||||
|
||||
void NativeWidgetPrivate::ClientDestroyedWidget() {}
|
||||
|
||||
void NativeWidgetPrivate::PaintAsActiveChanged() {}
|
||||
|
||||
void NativeWidgetPrivate::ShowWindowControlsMenu(const gfx::Point& point) {}
|
||||
|
@ -130,6 +130,17 @@ class VIEWS_EXPORT NativeWidgetPrivate : public NativeWidget {
|
||||
// hierarchy.
|
||||
virtual void ViewRemoved(View* view) = 0;
|
||||
|
||||
// Notifies the NativeWidget that its Widget was destroyed by the client.
|
||||
// NativeWidgets should override this to clear any references to its
|
||||
// associated Widget. The NativeWidget destruction will be initiated
|
||||
// separately by the host platform.
|
||||
//
|
||||
// Only relevant for CLIENT_OWNS_WIDGET ownership schemes. Not relevant for
|
||||
// other widget ownership schemes
|
||||
// - NATIVE_WIDGET_OWNS_WIDGET - NativeWidget initiates Widget destruction.
|
||||
// - WIDGET_OWNS_NATIVE_WIDGET - Widget synchronously destroys NativeWidget.
|
||||
virtual void ClientDestroyedWidget();
|
||||
|
||||
// Sets/Gets a native window property on the underlying native window object.
|
||||
// Returns NULL if the property does not exist. Setting the property value to
|
||||
// NULL removes the property.
|
||||
|
@ -281,6 +281,15 @@ Widget::~Widget() {
|
||||
native_widget_->Close();
|
||||
}
|
||||
HandleWidgetDestroying();
|
||||
|
||||
// Specifically in the case of CLIENT_OWNS_WIDGET the native widget is
|
||||
// notified to allow clearing of any widget-associated state. Do so before
|
||||
// the call to `HandleWidgetDestroyed()` below which will invalidate
|
||||
// `native_widget_`.
|
||||
if (native_widget_) {
|
||||
native_widget_->ClientDestroyedWidget();
|
||||
}
|
||||
|
||||
HandleWidgetDestroyed();
|
||||
if (widget_delegate_) {
|
||||
widget_delegate_->WidgetDestroying();
|
||||
|
@ -5795,6 +5795,23 @@ TEST_F(WidgetTest, ChildWidgetNotifiesObserverWhenReparented) {
|
||||
EXPECT_EQ(observer_2.child_widget(), nullptr);
|
||||
}
|
||||
|
||||
TEST_F(WidgetTest, NativeWidgetNotifiedOfWidgetDestructionForClientOwnsWidget) {
|
||||
auto widget = std::make_unique<Widget>();
|
||||
Widget::InitParams params = CreateParams(
|
||||
Widget::InitParams::CLIENT_OWNS_WIDGET, Widget::InitParams::TYPE_WINDOW);
|
||||
|
||||
auto native_widget =
|
||||
std::make_unique<testing::NiceMock<MockNativeWidget>>(widget.get());
|
||||
ON_CALL(*native_widget, CreateNonClientFrameView).WillByDefault([]() {
|
||||
return std::make_unique<NonClientFrameView>();
|
||||
});
|
||||
params.native_widget = native_widget.get();
|
||||
widget->Init(std::move(params));
|
||||
|
||||
EXPECT_CALL(*native_widget, ClientDestroyedWidget());
|
||||
widget.reset();
|
||||
}
|
||||
|
||||
// Parameterized test that verifies the behavior of SetAspectRatio with respect
|
||||
// to the excluded margin.
|
||||
class WidgetSetAspectRatioTest
|
||||
|
Reference in New Issue
Block a user