Commit 5872f9de authored by David Bienvenu's avatar David Bienvenu Committed by Chromium LUCI CQ

Prevent dropping tabs on occluded browser windows.

On Windows, check if the WindowTreeHost of the window being dropped on
is occluded, and if so, don't allow the drop.

This also enables occlusion calculation during window moving, if the
window being moved is a root window. And it ignores the dragged window
when determining if a window under it is occluded.

Bug: 820998
Change-Id: I8fd95ec3f9c9d1b40a848c697cce91125eff21c9
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1879606
Commit-Queue: David Bienvenu <davidbienvenu@chromium.org>
Reviewed-by: default avatarScott Violet <sky@chromium.org>
Cr-Commit-Position: refs/heads/master@{#835713}
parent ceffa1cb
......@@ -113,6 +113,10 @@
#define DESKTOP_BROWSER_FRAME_AURA DesktopBrowserFrameAura
#endif
#if defined(OS_WIN)
#include "ui/base/ui_base_features.h"
#endif
using content::WebContents;
using display::Display;
using ui_test_utils::GetDisplays;
......@@ -440,7 +444,16 @@ class DetachToBrowserTabDragControllerTest
public ::testing::WithParamInterface<const char*> {
public:
DetachToBrowserTabDragControllerTest() {
scoped_feature_list_.InitAndDisableFeature(features::kWebUITabStrip);
scoped_feature_list_.InitWithFeatures(
/*enabled_features=*/{},
/*disabled_features=*/{
features::kWebUITabStrip,
#if defined(OS_WIN)
// Disable NativeWinOcclusion to avoid it interfering with test
// for dragging over occluded browser window.
features::kCalculateNativeWinOcclusion,
#endif // OS_WIN
});
}
DetachToBrowserTabDragControllerTest(
const DetachToBrowserTabDragControllerTest&) = delete;
......@@ -1417,6 +1430,41 @@ IN_PROC_BROWSER_TEST_P(DetachToBrowserTabDragControllerTest,
EXPECT_FALSE(tab_strip2->GetWidget()->HasCapture());
}
#if defined(OS_WIN)
// Create two browsers, with the second one occluded, and drag from first over
// second. This should create a third browser, w/o bringing forward the second
// browser, because it's occluded.
IN_PROC_BROWSER_TEST_P(DetachToBrowserTabDragControllerTest,
DragToOccludedWindow) {
TabStrip* tab_strip = GetTabStripForBrowser(browser());
AddTabsAndResetBrowser(browser(), 1);
// Create another browser.
Browser* browser2 = CreateAnotherBrowserAndResize();
TabStrip* tab_strip2 = GetTabStripForBrowser(browser2);
// Mark the second brower as occluded. NativeWindow occlusion calculation has
// been disabled in test constructor, so we don't need an actual occluding
// window.
browser2->window()
->GetNativeWindow()
->GetHost()
->SetNativeWindowOcclusionState(aura::Window::OcclusionState::OCCLUDED);
// Drag a tab from first browser to middle of first tab of the second,
// occluded browser, and drop. This should create a third browser window.
ASSERT_TRUE(PressInput(GetCenterInScreenCoordinates(tab_strip->tab_at(1))));
ASSERT_TRUE(
DragInputToAsync(GetCenterInScreenCoordinates(tab_strip2->tab_at(0))));
ASSERT_TRUE(ReleaseInput());
EXPECT_EQ(3u, browser_list->size());
}
#endif // OS_WIN
namespace {
// WindowFinder that calls OnMouseCaptureLost() from
......
......@@ -351,6 +351,8 @@ void NativeWindowOcclusionTrackerWin::WindowOcclusionCalculator::
DisableOcclusionTrackingForWindow(HWND hwnd) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
root_window_hwnds_occlusion_state_.erase(hwnd);
if (moving_window_ == hwnd)
moving_window_ = 0;
if (root_window_hwnds_occlusion_state_.empty()) {
UnregisterEventHooks();
if (occlusion_update_timer_.IsRunning())
......@@ -612,6 +614,10 @@ bool NativeWindowOcclusionTrackerWin::WindowOcclusionCalculator::
return true;
}
// Ignore moving windows when deciding if windows under it are occluded.
if (hwnd == moving_window_)
return true;
// Check if |hwnd| is a root window; if so, we're done figuring out
// if it's occluded because we've seen all the windows "over" it.
auto it = root_window_hwnds_occlusion_state_.find(hwnd);
......@@ -688,22 +694,31 @@ void NativeWindowOcclusionTrackerWin::WindowOcclusionCalculator::
// Let occlusion calculation fix occlusion state.
}
}
// Don't continually calculate occlusion while a window is moving, but rather
// once at the beginning and once at the end.
if (event == EVENT_SYSTEM_MOVESIZESTART) {
window_is_moving_ = true;
// Don't continually calculate occlusion while a window is moving (unless it's
// a root window), but instead once at the beginning and once at the end.
// Remember the window being moved so if it's a root window, we can ignore
// it when deciding if windows under it are occluded.
else if (event == EVENT_SYSTEM_MOVESIZESTART) {
moving_window_ = hwnd;
} else if (event == EVENT_SYSTEM_MOVESIZEEND) {
window_is_moving_ = false;
} else if (window_is_moving_) {
moving_window_ = 0;
} else if (moving_window_ != 0) {
if (event == EVENT_OBJECT_LOCATIONCHANGE ||
event == EVENT_OBJECT_STATECHANGE) {
return;
// Ignore move events if it's not a root window that's being moved. If it
// is a root window, we want to calculate occlusion to support tab
// dragging to windows that were occluded when the drag was started but
// are no longer occluded.
if (root_window_hwnds_occlusion_state_.find(hwnd) ==
root_window_hwnds_occlusion_state_.end()) {
return;
}
} else {
// If we get an event that isn't a location/state change, then we probably
// missed the movesizeend notification, or got events out of order. In
// that case, we want to go back to normal occlusion calculation.
moving_window_ = 0;
}
// If we get an event that isn't a location/state change, then we probably
// missed the movesizeend notification, or got events out of order. In
// that case, we want to go back to calculating occlusion.
window_is_moving_ = false;
}
// ProcessEventHookCallback is called from the task_runner's PeekMessage
......
......@@ -239,6 +239,11 @@ class AURA_EXPORT NativeWindowOcclusionTrackerWin
// showing.
bool showing_thumbnails_ = false;
// Used to keep track of the window that's currently moving. That window
// is ignored for calculation occlusion so that tab dragging won't
// ignore windows occluded by the dragged window.
HWND moving_window_ = 0;
// Only used on Win10+.
Microsoft::WRL::ComPtr<IVirtualDesktopManager> virtual_desktop_manager_;
......
......@@ -16,7 +16,7 @@ gfx::NativeWindow LocalProcessWindowFinder::GetProcessWindowAtPoint(
const gfx::Point& screen_loc,
const std::set<HWND>& ignore,
ScreenWin* screen_win) {
LocalProcessWindowFinder finder(screen_loc, ignore);
LocalProcessWindowFinder finder(screen_loc, screen_win, ignore);
// Windows 8 has a window that appears first in the list of iterated
// windows, yet is not visually on top of everything.
// TODO(sky): figure out a better way to ignore this window.
......@@ -44,15 +44,22 @@ bool LocalProcessWindowFinder::ShouldStopIterating(HWND hwnd) {
if (IsWindowVisible(hwnd) && GetWindowRect(hwnd, &r) &&
PtInRect(&r, screen_loc_.ToPOINT())) {
result_ = hwnd;
// Don't set result_ if the window is occluded, because there is at least
// one window covering the browser window. E.g., tab drag drop shouldn't
// drop on an occluded browser window.
gfx::NativeWindow native_window =
screen_win_->GetNativeWindowFromHWND(hwnd);
if (!native_window || !screen_win_->IsNativeWindowOccluded(native_window))
result_ = hwnd;
return true;
}
return false;
}
LocalProcessWindowFinder::LocalProcessWindowFinder(const gfx::Point& screen_loc,
ScreenWin* screen_win,
const std::set<HWND>& ignore)
: BaseWindowFinderWin(ignore), result_(nullptr) {
: BaseWindowFinderWin(ignore), result_(nullptr), screen_win_(screen_win) {
if (base::win::GetVersion() >= base::win::Version::WIN10) {
::CoCreateInstance(__uuidof(VirtualDesktopManager), nullptr, CLSCTX_ALL,
IID_PPV_ARGS(&virtual_desktop_manager_));
......
......@@ -32,6 +32,7 @@ class LocalProcessWindowFinder : public BaseWindowFinderWin {
private:
LocalProcessWindowFinder(const gfx::Point& screen_loc,
ScreenWin* screen_win,
const std::set<HWND>& ignore);
LocalProcessWindowFinder(const LocalProcessWindowFinder& finder) = delete;
LocalProcessWindowFinder& operator=(const LocalProcessWindowFinder& finder) =
......@@ -45,6 +46,10 @@ class LocalProcessWindowFinder : public BaseWindowFinderWin {
// ShouldStopIterating if an appropriate window is found.
HWND result_;
// ScreenWin we're looking on. Used to access WindowTreeHost, which
// ui/display code can't access directly.
ScreenWin* screen_win_;
// Only used on Win10+.
Microsoft::WRL::ComPtr<IVirtualDesktopManager> virtual_desktop_manager_;
};
......
......@@ -662,6 +662,11 @@ gfx::NativeWindow ScreenWin::GetNativeWindowFromHWND(HWND hwnd) const {
return nullptr;
}
bool ScreenWin::IsNativeWindowOccluded(gfx::NativeWindow window) const {
NOTREACHED();
return false;
}
ScreenWin::ScreenWin(bool initialize) {
DCHECK(!g_instance);
g_instance = this;
......
......@@ -154,6 +154,9 @@ class DISPLAY_EXPORT ScreenWin : public Screen,
// Returns the NativeWindow associated with the HWND.
virtual gfx::NativeWindow GetNativeWindowFromHWND(HWND hwnd) const;
// Returns true if the native window is occluded.
virtual bool IsNativeWindowOccluded(gfx::NativeWindow window) const;
protected:
ScreenWin(bool initialize);
......
......@@ -28,6 +28,11 @@ gfx::NativeWindow DesktopScreenWin::GetNativeWindowFromHWND(HWND hwnd) const {
: gfx::kNullNativeWindow;
}
bool DesktopScreenWin::IsNativeWindowOccluded(gfx::NativeWindow window) const {
return window->GetHost()->GetNativeWindowOcclusionState() ==
aura::Window::OcclusionState::OCCLUDED;
}
////////////////////////////////////////////////////////////////////////////////
display::Screen* CreateDesktopScreen() {
......
......@@ -21,6 +21,7 @@ class VIEWS_EXPORT DesktopScreenWin : public display::win::ScreenWin {
// display::win::ScreenWin:
HWND GetHWNDFromNativeWindow(gfx::NativeWindow window) const override;
gfx::NativeWindow GetNativeWindowFromHWND(HWND hwnd) const override;
bool IsNativeWindowOccluded(gfx::NativeWindow window) const override;
display::Screen* const old_screen_ = display::Screen::SetScreenInstance(this);
};
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment