Commit 03b56190 authored by David Bienvenu's avatar David Bienvenu Committed by Commit Bot

Native window occlusion tracking for windows.

This implements native window occlusion tracking on windows, under flag/experiment control. It uses WinEvent event hooks and EnumWindows to track and calculate occlusion.

It is not yet hooked up to the code that treats occluded windows differently.

Bug: 813093
Change-Id: Ic9b473b8cad38e7b1653566f28917164bc92e21c
Reviewed-on: https://chromium-review.googlesource.com/c/1140752
Commit-Queue: David Bienvenu <davidbienvenu@chromium.org>
Reviewed-by: default avatarScott Violet <sky@chromium.org>
Reviewed-by: default avatarFrançois Doray <fdoray@chromium.org>
Cr-Commit-Position: refs/heads/master@{#603619}
parent f0c17a60
......@@ -4344,6 +4344,12 @@ const FeatureEntry kFeatureEntries[] = {
flag_descriptions::kEnableHomeLauncherGesturesDescription, kOsCrOS,
FEATURE_VALUE_TYPE(app_list_features::kEnableHomeLauncherGestures)},
#endif
#if defined(OS_WIN)
{"calculate-native-win-occlusion",
flag_descriptions::kCalculateNativeWinOcclusionName,
flag_descriptions::kCalculateNativeWinOcclusionDescription, kOsWin,
FEATURE_VALUE_TYPE(features::kCalculateNativeWinOcclusion)},
#endif // OS_WIN
#if !defined(OS_ANDROID)
{"happiness-tarcking-surveys-for-desktop",
......
......@@ -2858,6 +2858,12 @@ const char kGoogleBrandedContextMenuDescription[] =
#if defined(OS_WIN)
const char kCalculateNativeWinOcclusionName[] =
"Calculate window occlusion on Windows";
const char kCalculateNativeWinOcclusionDescription[] =
"Calculate window occlusion on Windows will be used in the future "
"to throttle and potentially unload foreground tabs in occluded windows";
const char kCloudPrintXpsName[] = "XPS in Google Cloud Print";
const char kCloudPrintXpsDescription[] =
"XPS enables advanced options for classic printers connected to the Cloud "
......
......@@ -1720,6 +1720,9 @@ extern const char kGoogleBrandedContextMenuDescription[];
#if defined(OS_WIN)
extern const char kCalculateNativeWinOcclusionName[];
extern const char kCalculateNativeWinOcclusionDescription[];
extern const char kCloudPrintXpsName[];
extern const char kCloudPrintXpsDescription[];
......
......@@ -4803,6 +4803,7 @@ if (!is_android) {
if (use_aura) {
sources += [ "../browser/ui/views/drag_and_drop_interactive_uitest.cc" ]
deps += [ "//ui/aura:aura_interactive_ui_tests" ]
} else {
sources -= [
"base/interactive_test_utils_aura.cc",
......
......@@ -29198,6 +29198,7 @@ from previous Chrome versions.
<int value="-1536293422" label="SharedArrayBuffer:enabled"/>
<int value="-1536242739" label="security-chip"/>
<int value="-1535758690" label="AutoplayIgnoreWebAudio:disabled"/>
<int value="-1533258008" label="CalculateNativeWinOcclusion:enabled"/>
<int value="-1532035450" label="DragTabsInTabletMode:disabled"/>
<int value="-1532014193" label="disable-encryption-migration"/>
<int value="-1528455406" label="OmniboxPedalSuggestions:enabled"/>
......@@ -31092,6 +31093,7 @@ from previous Chrome versions.
<int value="2043321329" label="OfflinePagesPrefetchingUI:disabled"/>
<int value="2056572020" label="EnableUsernameCorrection:disabled"/>
<int value="2058283872" label="CCTModuleCache:disabled"/>
<int value="2058439723" label="CalculateNativeWinOcclusion:disabled"/>
<int value="2059322877" label="new-avatar-menu"/>
<int value="2063091429" label="OfflinePagesSharing:enabled"/>
<int value="2067634730" label="LsdPermissionPrompt:disabled"/>
......@@ -138,6 +138,8 @@ jumbo_component("aura") {
"mus/window_tree_client_delegate.cc",
"mus/window_tree_host_mus.cc",
"mus/window_tree_host_mus_init_params.cc",
"native_window_occlusion_tracker_win.cc",
"native_window_occlusion_tracker_win.h",
"null_window_targeter.cc",
"scoped_keyboard_hook.cc",
"scoped_simple_keyboard_hook.cc",
......@@ -155,6 +157,12 @@ jumbo_component("aura") {
"window_tree_host_platform.cc",
]
# aura_interactive_ui_tests needs access to native_window_occlusion_tracker.h.
friend = [
":aura_interactive_ui_tests",
":aura_unittests",
]
defines = [ "AURA_IMPLEMENTATION" ]
deps = [
......@@ -394,6 +402,10 @@ test("aura_unittests") {
"window_unittest.cc",
]
if (is_win) {
sources += [ "native_window_occlusion_tracker_unittest.cc" ]
}
deps = [
":test_support",
"//base/test:test_support",
......@@ -424,3 +436,29 @@ test("aura_unittests") {
"//third_party/mesa_headers",
]
}
# This target is added as a dependency of browser interactive_ui_tests. It must
# be source_set, otherwise the linker will drop the tests as dead code.
source_set("aura_interactive_ui_tests") {
testonly = true
if (is_win) {
sources = [
"native_window_occlusion_tracker_win_interactive_test.cc",
]
deps = [
":aura",
":test_support",
"//base/test:test_support",
"//net",
"//testing/gtest",
"//ui/base/ime:ime",
"//ui/display:display",
"//ui/gfx",
"//ui/gfx/geometry",
"//ui/gl:test_support",
"//ui/gl/init",
"//ui/views:views",
]
}
}
......@@ -20,6 +20,7 @@ include_rules = [
"+ui/display",
"+ui/events",
"+ui/gfx",
"+ui/gl/test",
"+ui/metro_viewer", # TODO(beng): investigate moving remote_root_window_host
# to ui/metro_viewer.
"+ui/ozone/public",
......
// Copyright 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "ui/aura/native_window_occlusion_tracker_win.h"
#include <winuser.h>
#include "base/win/scoped_gdi_object.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "ui/aura/test/aura_test_base.h"
#include "ui/gfx/geometry/rect.h"
#include "ui/gfx/win/window_impl.h"
namespace aura {
// Test wrapper around native window HWND.
class TestNativeWindow : public gfx::WindowImpl {
public:
TestNativeWindow() {}
~TestNativeWindow() override;
private:
// Overridden from gfx::WindowImpl:
BOOL ProcessWindowMessage(HWND window,
UINT message,
WPARAM w_param,
LPARAM l_param,
LRESULT& result,
DWORD msg_map_id) override {
return FALSE; // Results in DefWindowProc().
}
DISALLOW_COPY_AND_ASSIGN(TestNativeWindow);
};
TestNativeWindow::~TestNativeWindow() {
if (hwnd())
DestroyWindow(hwnd());
}
// This class currently tests the behavior of
// NativeWindowOcclusionTrackerWin::IsWindowVisibleAndFullyOpaque with hwnds
// with various attributes (e.g., minimized, transparent, etc).
class NativeWindowOcclusionTrackerTest : public test::AuraTestBase {
public:
NativeWindowOcclusionTrackerTest() {}
TestNativeWindow* native_win() { return native_win_.get(); }
HWND CreateNativeWindow(DWORD ex_style) {
native_win_ = std::make_unique<TestNativeWindow>();
native_win_->set_window_style(WS_OVERLAPPEDWINDOW | WS_CLIPCHILDREN);
native_win_->set_window_ex_style(ex_style);
gfx::Rect bounds(0, 0, 100, 100);
native_win_->Init(nullptr, bounds);
HWND hwnd = native_win_->hwnd();
base::win::ScopedRegion region(CreateRectRgn(0, 0, 0, 0));
if (GetWindowRgn(hwnd, region.get()) == COMPLEXREGION) {
// On Windows 7, the newly created window has a complex region, which
// means it will be ignored during the occlusion calculation. So, force
// it to have a simple region so that we get test coverage on win 7.
RECT bounding_rect;
EXPECT_TRUE(GetWindowRect(hwnd, &bounding_rect));
base::win::ScopedRegion rectangular_region(
CreateRectRgnIndirect(&bounding_rect));
SetWindowRgn(hwnd, rectangular_region.get(), /*redraw=*/TRUE);
}
ShowWindow(hwnd, SW_SHOWNORMAL);
EXPECT_TRUE(UpdateWindow(hwnd));
return hwnd;
}
// Wrapper around IsWindowVisibleAndFullyOpaque so only the test class
// needs to be a friend of NativeWindowOcclusionTrackerWin.
bool CheckWindowVisibleAndFullyOpaque(HWND hwnd, gfx::Rect* win_rect) {
bool ret = NativeWindowOcclusionTrackerWin::IsWindowVisibleAndFullyOpaque(
hwnd, win_rect);
// In general, if IsWindowVisibleAndFullyOpaque returns false, the
// returned rect should not be altered.
if (!ret)
EXPECT_EQ(*win_rect, gfx::Rect(0, 0, 0, 0));
return ret;
}
private:
std::unique_ptr<TestNativeWindow> native_win_;
DISALLOW_COPY_AND_ASSIGN(NativeWindowOcclusionTrackerTest);
};
TEST_F(NativeWindowOcclusionTrackerTest, VisibleOpaqueWindow) {
HWND hwnd = CreateNativeWindow(/*ex_style=*/0);
gfx::Rect returned_rect;
// Normal windows should be visible.
EXPECT_TRUE(CheckWindowVisibleAndFullyOpaque(hwnd, &returned_rect));
// Check that the returned rect == the actual window rect of the hwnd.
RECT win_rect;
ASSERT_TRUE(GetWindowRect(hwnd, &win_rect));
EXPECT_EQ(returned_rect, gfx::Rect(win_rect));
}
TEST_F(NativeWindowOcclusionTrackerTest, MinimizedWindow) {
HWND hwnd = CreateNativeWindow(/*ex_style=*/0);
gfx::Rect win_rect;
ShowWindow(hwnd, SW_MINIMIZE);
// Minimized windows are not considered visible.
EXPECT_FALSE(CheckWindowVisibleAndFullyOpaque(hwnd, &win_rect));
}
TEST_F(NativeWindowOcclusionTrackerTest, TransparentWindow) {
HWND hwnd = CreateNativeWindow(WS_EX_TRANSPARENT);
gfx::Rect win_rect;
// Transparent windows are not considered visible and opaque.
EXPECT_FALSE(CheckWindowVisibleAndFullyOpaque(hwnd, &win_rect));
}
TEST_F(NativeWindowOcclusionTrackerTest, ToolWindow) {
HWND hwnd = CreateNativeWindow(WS_EX_TOOLWINDOW);
gfx::Rect win_rect;
// Tool windows are not considered visible and opaque.
EXPECT_FALSE(CheckWindowVisibleAndFullyOpaque(hwnd, &win_rect));
}
TEST_F(NativeWindowOcclusionTrackerTest, LayeredAlphaWindow) {
HWND hwnd = CreateNativeWindow(WS_EX_LAYERED);
gfx::Rect win_rect;
BYTE alpha = 1;
DWORD flags = LWA_ALPHA;
COLORREF color_ref = RGB(1, 1, 1);
SetLayeredWindowAttributes(hwnd, color_ref, alpha, flags);
// Layered windows with alpha < 255 are not considered visible and opaque.
EXPECT_FALSE(CheckWindowVisibleAndFullyOpaque(hwnd, &win_rect));
}
TEST_F(NativeWindowOcclusionTrackerTest, LayeredNonAlphaWindow) {
HWND hwnd = CreateNativeWindow(WS_EX_LAYERED);
gfx::Rect win_rect;
BYTE alpha = 1;
DWORD flags = 0;
COLORREF color_ref = RGB(1, 1, 1);
SetLayeredWindowAttributes(hwnd, color_ref, alpha, flags);
// Layered non alpha windows are considered visible and opaque.
EXPECT_TRUE(CheckWindowVisibleAndFullyOpaque(hwnd, &win_rect));
}
TEST_F(NativeWindowOcclusionTrackerTest, ComplexRegionWindow) {
HWND hwnd = CreateNativeWindow(/*ex_style=*/0);
gfx::Rect win_rect;
// Create a region with rounded corners, which should be a complex region.
base::win::ScopedRegion region(CreateRoundRectRgn(1, 1, 100, 100, 5, 5));
SetWindowRgn(hwnd, region.get(), /*redraw=*/TRUE);
// Windows with complex regions are not considered visible and fully opaque.
EXPECT_FALSE(CheckWindowVisibleAndFullyOpaque(hwnd, &win_rect));
}
} // namespace aura
// Copyright 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "ui/aura/native_window_occlusion_tracker_win.h"
#include <memory>
#include "base/memory/scoped_refptr.h"
#include "base/task/post_task.h"
#include "base/task/task_traits.h"
#include "base/threading/sequenced_task_runner_handle.h"
#include "base/threading/thread_task_runner_handle.h"
#include "base/win/scoped_gdi_object.h"
#include "ui/aura/window_tree_host.h"
namespace aura {
namespace {
// ~16 ms = time between frames when frame rate is 60 FPS.
const base::TimeDelta kUpdateOcclusionDelay =
base::TimeDelta::FromMilliseconds(16);
NativeWindowOcclusionTrackerWin* g_tracker = nullptr;
} // namespace
NativeWindowOcclusionTrackerWin*
NativeWindowOcclusionTrackerWin::GetOrCreateInstance() {
if (!g_tracker)
g_tracker = new NativeWindowOcclusionTrackerWin();
return g_tracker;
}
void NativeWindowOcclusionTrackerWin::Enable(Window* window) {
DCHECK(window->IsRootWindow());
if (window->HasObserver(this)) {
DCHECK(FALSE) << "window shouldn't already be observing occlusion tracker";
return;
}
// Add this as an observer so that we can be notified
// when it's no longer true that all windows are minimized, and when the
// window is destroyed.
HWND root_window_hwnd = window->GetHost()->GetAcceleratedWidget();
window->AddObserver(this);
// Remember this mapping from hwnd to Window*.
hwnd_root_window_map_[root_window_hwnd] = window;
// Notify the occlusion thread of the new HWND to track.
update_occlusion_task_runner_->PostTask(
FROM_HERE,
base::BindOnce(
&WindowOcclusionCalculator::EnableOcclusionTrackingForWindow,
base::Unretained(occlusion_calculator_.get()), root_window_hwnd));
}
void NativeWindowOcclusionTrackerWin::Disable(Window* window) {
DCHECK(window->IsRootWindow());
HWND root_window_hwnd = window->GetHost()->GetAcceleratedWidget();
// Check that the root_window_hwnd doesn't get cleared before this is called.
DCHECK(root_window_hwnd);
hwnd_root_window_map_.erase(root_window_hwnd);
window->RemoveObserver(this);
update_occlusion_task_runner_->PostTask(
FROM_HERE,
base::BindOnce(
&WindowOcclusionCalculator::DisableOcclusionTrackingForWindow,
base::Unretained(occlusion_calculator_.get()), root_window_hwnd));
}
void NativeWindowOcclusionTrackerWin::OnWindowVisibilityChanged(Window* window,
bool visible) {
if (!window->IsRootWindow())
return;
window->GetHost()->SetNativeWindowOcclusionState(
visible ? Window::OcclusionState::UNKNOWN
: Window::OcclusionState::HIDDEN);
update_occlusion_task_runner_->PostTask(
FROM_HERE,
base::BindOnce(&WindowOcclusionCalculator::HandleVisibilityChanged,
base::Unretained(occlusion_calculator_.get()), visible));
}
void NativeWindowOcclusionTrackerWin::OnWindowDestroying(Window* window) {
Disable(window);
}
NativeWindowOcclusionTrackerWin::NativeWindowOcclusionTrackerWin()
: // Use a COMSTATaskRunner so that registering and unregistering
// event hooks will happen on the same thread, as required by Windows,
// and the task runner will have a message loop to call
// EventHookCallback.
update_occlusion_task_runner_(base::CreateCOMSTATaskRunnerWithTraits(
{base::MayBlock(),
// This may be needed to determine that a window is no longer
// occluded.
base::TaskPriority::USER_VISIBLE,
// Occlusion calculation doesn't need to happen on shutdown.
// event hooks should also be cleaned up by Windows.
base::TaskShutdownBehavior::SKIP_ON_SHUTDOWN})) {
occlusion_calculator_ = std::make_unique<WindowOcclusionCalculator>(
update_occlusion_task_runner_, base::SequencedTaskRunnerHandle::Get());
}
NativeWindowOcclusionTrackerWin::~NativeWindowOcclusionTrackerWin() {
// This shouldn't be reached, because if it is, |occlusion_calculator_| will
// be deleted on the ui thread, which is problematic if there tasks scheduled
// on the background thread.
NOTREACHED();
}
// static
bool NativeWindowOcclusionTrackerWin::IsWindowVisibleAndFullyOpaque(
HWND hwnd,
gfx::Rect* window_rect) {
// Filter out windows that are not “visible”, IsWindowVisible().
if (!IsWindow(hwnd) || !IsWindowVisible(hwnd))
return false;
// Filter out minimized windows.
if (IsIconic(hwnd))
return false;
LONG ex_styles = GetWindowLong(hwnd, GWL_EXSTYLE);
// Filter out “transparent” windows, windows where the mouse clicks fall
// through them.
if (ex_styles & WS_EX_TRANSPARENT)
return false;
// Filter out “tool windows”, which are floating windows that do not appear on
// the taskbar or ALT-TAB. Floating windows can have larger window rectangles
// than what is visible to the user, so by filtering them out we will avoid
// incorrectly marking native windows as occluded.
if (ex_styles & WS_EX_TOOLWINDOW)
return false;
// Filter out layered windows that are not opaque or that set a transparency
// colorkey.
if (ex_styles & WS_EX_LAYERED) {
BYTE alpha;
DWORD flags;
if (GetLayeredWindowAttributes(hwnd, nullptr, &alpha, &flags)) {
if (flags & LWA_ALPHA && alpha < 255)
return false;
if (flags & LWA_COLORKEY)
return false;
}
}
// Filter out windows that do not have a simple rectangular region.
base::win::ScopedRegion region(CreateRectRgn(0, 0, 0, 0));
if (GetWindowRgn(hwnd, region.get()) == COMPLEXREGION)
return false;
RECT win_rect;
// Filter out windows that take up zero area. The call to GetWindowRect is one
// of the most expensive parts of this function, so it is last.
if (!GetWindowRect(hwnd, &win_rect))
return false;
if (IsRectEmpty(&win_rect))
return false;
*window_rect = gfx::Rect(win_rect);
return true;
}
void NativeWindowOcclusionTrackerWin::UpdateOcclusionState(
const base::flat_map<HWND, Window::OcclusionState>&
root_window_hwnds_occlusion_state) {
for (const auto& root_window_pair : root_window_hwnds_occlusion_state) {
auto it = hwnd_root_window_map_.find(root_window_pair.first);
// The window was destroyed while processing occlusion.
if (it == hwnd_root_window_map_.end())
continue;
Window* root_window = it->second;
// Check Window::IsVisible here, on the UI thread, because it can't be
// checked on the occlusion calculation thread.
it->second->GetHost()->SetNativeWindowOcclusionState(
!root_window->IsVisible() ? Window::OcclusionState::HIDDEN
: root_window_pair.second);
}
}
NativeWindowOcclusionTrackerWin::WindowOcclusionCalculator::
WindowOcclusionCalculator(
scoped_refptr<base::SequencedTaskRunner> task_runner,
scoped_refptr<base::SequencedTaskRunner> ui_thread_task_runner)
: task_runner_(task_runner), ui_thread_task_runner_(ui_thread_task_runner) {
DETACH_FROM_SEQUENCE(sequence_checker_);
}
NativeWindowOcclusionTrackerWin::WindowOcclusionCalculator::
~WindowOcclusionCalculator() {
DCHECK(global_event_hooks_.empty());
}
void NativeWindowOcclusionTrackerWin::WindowOcclusionCalculator::
EnableOcclusionTrackingForWindow(HWND hwnd) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
NativeWindowOcclusionState default_state;
root_window_hwnds_occlusion_state_[hwnd] = default_state;
if (global_event_hooks_.empty())
RegisterEventHooks();
// Schedule an occlusion calculation so that the newly tracked window does
// not have a stale occlusion status.
ScheduleOcclusionCalculationIfNeeded();
}
void NativeWindowOcclusionTrackerWin::WindowOcclusionCalculator::
DisableOcclusionTrackingForWindow(HWND hwnd) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
root_window_hwnds_occlusion_state_.erase(hwnd);
if (root_window_hwnds_occlusion_state_.empty()) {
UnregisterEventHooks();
if (occlusion_update_timer_.IsRunning())
occlusion_update_timer_.Stop();
}
}
void NativeWindowOcclusionTrackerWin::WindowOcclusionCalculator::
HandleVisibilityChanged(bool visible) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
// May have gone from having no visible windows to having one, in
// which case we need to register event hooks.
if (visible)
MaybeRegisterEventHooks();
}
void NativeWindowOcclusionTrackerWin::WindowOcclusionCalculator::
MaybeRegisterEventHooks() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (global_event_hooks_.empty())
RegisterEventHooks();
}
// static
void CALLBACK
NativeWindowOcclusionTrackerWin::WindowOcclusionCalculator::EventHookCallback(
HWINEVENTHOOK hWinEventHook,
DWORD event,
HWND hwnd,
LONG idObject,
LONG idChild,
DWORD dwEventThread,
DWORD dwmsEventTime) {
g_tracker->occlusion_calculator_->ProcessEventHookCallback(event, hwnd,
idObject, idChild);
}
// static
BOOL CALLBACK NativeWindowOcclusionTrackerWin::WindowOcclusionCalculator::
ComputeNativeWindowOcclusionStatusCallback(HWND hwnd, LPARAM lParam) {
return g_tracker->occlusion_calculator_
->ProcessComputeNativeWindowOcclusionStatusCallback(
hwnd, reinterpret_cast<base::flat_set<DWORD>*>(lParam));
}
// static
BOOL CALLBACK NativeWindowOcclusionTrackerWin::WindowOcclusionCalculator::
UpdateVisibleWindowProcessIdsCallback(HWND hwnd, LPARAM lParam) {
g_tracker->occlusion_calculator_
->ProcessUpdateVisibleWindowProcessIdsCallback(hwnd);
return TRUE;
}
void NativeWindowOcclusionTrackerWin::WindowOcclusionCalculator::
UpdateVisibleWindowProcessIds() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
pids_for_location_change_hook_.clear();
EnumWindows(&UpdateVisibleWindowProcessIdsCallback, 0);
}
void NativeWindowOcclusionTrackerWin::WindowOcclusionCalculator::
ComputeNativeWindowOcclusionStatus() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (root_window_hwnds_occlusion_state_.empty())
return;
// Set up initial conditions for occlusion calculation.
bool all_minimized = true;
for (auto& root_window_pair : root_window_hwnds_occlusion_state_) {
root_window_pair.second.unoccluded_region.setEmpty();
HWND hwnd = root_window_pair.first;
// IsIconic() checks for a minimized window. Immediately set the state of
// minimized windows to HIDDEN.
if (IsIconic(hwnd)) {
root_window_pair.second.occlusion_state = Window::OcclusionState::HIDDEN;
} else {
root_window_pair.second.occlusion_state = Window::OcclusionState::UNKNOWN;
RECT window_rect;
if (GetWindowRect(hwnd, &window_rect) != 0) {
root_window_pair.second.unoccluded_region =
SkRegion(SkIRect::MakeLTRB(window_rect.left, window_rect.top,
window_rect.right, window_rect.bottom));
}
// If call to GetWindowRect fails, window will be treated as occluded,
// because unoccluded_region will be empty.
all_minimized = false;
}
}
// Unregister event hooks if all native windows are minimized.
if (all_minimized) {
UnregisterEventHooks();
} else {
base::flat_set<DWORD> current_pids_with_visible_windows;
// Calculate unoccluded region if there is a non-minimized native window.
// Also compute |current_pids_with_visible_windows| as we enumerate
// the windows.
EnumWindows(&ComputeNativeWindowOcclusionStatusCallback,
reinterpret_cast<LPARAM>(&current_pids_with_visible_windows));
// Check if |pids_for_location_change_hook_| has any pids of processes
// currently without visible windows. If so, unhook the win event,
// remove the pid from |pids_for_location_change_hook_| and remove
// the corresponding event hook from |process_event_hooks_|.
base::flat_set<DWORD> pids_to_remove;
for (auto loc_change_pid : pids_for_location_change_hook_) {
if (current_pids_with_visible_windows.find(loc_change_pid) ==
current_pids_with_visible_windows.end()) {
// Remove the event hook from our map, and unregister the event hook.
// It's possible the eventhook will no longer be valid, but if we don't
// unregister the event hook, a process that toggles between having
// visible windows and not having visible windows could cause duplicate
// event hooks to get registered for the process.
UnhookWinEvent(process_event_hooks_[loc_change_pid]);
process_event_hooks_.erase(loc_change_pid);
pids_to_remove.insert(loc_change_pid);
}
}
if (!pids_to_remove.empty()) {
// EraseIf is O(n) so erase pids not found in one fell swoop.
base::EraseIf(pids_for_location_change_hook_,
[&pids_to_remove](DWORD pid) {
return pids_to_remove.find(pid) != pids_to_remove.end();
});
}
}
// Determine new occlusion status and post a task to the browser ui
// thread to update the window occlusion state on the root windows.
base::flat_map<HWND, Window::OcclusionState> window_occlusion_states;
for (auto& root_window_pair : root_window_hwnds_occlusion_state_) {
Window::OcclusionState new_state;
if (root_window_pair.second.occlusion_state !=
Window::OcclusionState::UNKNOWN) {
new_state = root_window_pair.second.occlusion_state;
} else {
new_state = root_window_pair.second.unoccluded_region.isEmpty()
? Window::OcclusionState::OCCLUDED
: Window::OcclusionState::VISIBLE;
}
window_occlusion_states[root_window_pair.first] = new_state;
root_window_pair.second.occlusion_state = new_state;
}
ui_thread_task_runner_->PostTask(
FROM_HERE,
base::BindOnce(&NativeWindowOcclusionTrackerWin::UpdateOcclusionState,
base::Unretained(g_tracker), window_occlusion_states));
}
void NativeWindowOcclusionTrackerWin::WindowOcclusionCalculator::
ScheduleOcclusionCalculationIfNeeded() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (!occlusion_update_timer_.IsRunning())
occlusion_update_timer_.Start(
FROM_HERE, kUpdateOcclusionDelay, this,
&WindowOcclusionCalculator::ComputeNativeWindowOcclusionStatus);
}
void NativeWindowOcclusionTrackerWin::WindowOcclusionCalculator::
RegisterGlobalEventHook(UINT event_min, UINT event_max) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
HWINEVENTHOOK event_hook =
SetWinEventHook(event_min, event_max, nullptr, &EventHookCallback, 0, 0,
WINEVENT_OUTOFCONTEXT);
global_event_hooks_.push_back(event_hook);
}
void NativeWindowOcclusionTrackerWin::WindowOcclusionCalculator::
RegisterEventHookForProcess(DWORD pid) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
pids_for_location_change_hook_.insert(pid);
process_event_hooks_[pid] = SetWinEventHook(
EVENT_OBJECT_LOCATIONCHANGE, EVENT_OBJECT_LOCATIONCHANGE, nullptr,
&EventHookCallback, pid, 0, WINEVENT_OUTOFCONTEXT);
}
void NativeWindowOcclusionTrackerWin::WindowOcclusionCalculator::
RegisterEventHooks() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(global_event_hooks_.empty());
// Detects native window move (drag) and resizing events.
RegisterGlobalEventHook(EVENT_SYSTEM_MOVESIZESTART, EVENT_SYSTEM_MOVESIZEEND);
// Detects native window minimize and restore from taskbar events.
RegisterGlobalEventHook(EVENT_SYSTEM_MINIMIZESTART, EVENT_SYSTEM_MINIMIZEEND);
// Detects foreground window changing.
RegisterGlobalEventHook(EVENT_SYSTEM_FOREGROUND, EVENT_SYSTEM_FOREGROUND);
// Detects object state changes, e.g., enable/disable state, native window
// maximize and native window restore events.
RegisterGlobalEventHook(EVENT_OBJECT_STATECHANGE, EVENT_OBJECT_STATECHANGE);
// Determine which subset of processes to set EVENT_OBJECT_LOCATIONCHANGE on
// because otherwise event throughput is very high, as it generates events
// for location changes of all objects, including the mouse moving on top of a
// window.
UpdateVisibleWindowProcessIds();
for (DWORD pid : pids_for_location_change_hook_)
RegisterEventHookForProcess(pid);
}
void NativeWindowOcclusionTrackerWin::WindowOcclusionCalculator::
UnregisterEventHooks() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
for (HWINEVENTHOOK event_hook : global_event_hooks_)
UnhookWinEvent(event_hook);
global_event_hooks_.clear();
for (DWORD pid : pids_for_location_change_hook_)
UnhookWinEvent(process_event_hooks_[pid]);
process_event_hooks_.clear();
pids_for_location_change_hook_.clear();
window_is_moving_ = false;
}
bool NativeWindowOcclusionTrackerWin::WindowOcclusionCalculator::
ProcessComputeNativeWindowOcclusionStatusCallback(
HWND hwnd,
base::flat_set<DWORD>* current_pids_with_visible_windows) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
gfx::Rect window_rect;
// 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.
// TODO(davidbienvenu): Explore checking if occlusion state has been
// computed for all |root_window_hwnds_occlusion_state_|, and if so, skipping
// further oclcusion calculations. However, we still want to keep computing
// |current_pids_with_visible_windows_|, so this function always returns true.
for (auto& root_window_pair : root_window_hwnds_occlusion_state_) {
if (hwnd == root_window_pair.first) {
if (root_window_pair.second.occlusion_state ==
Window::OcclusionState::HIDDEN) {
break;
}
root_window_pair.second.occlusion_state =
root_window_pair.second.unoccluded_region.isEmpty()
? Window::OcclusionState::OCCLUDED
: Window::OcclusionState::VISIBLE;
break;
}
}
if (!IsWindowVisibleAndFullyOpaque(hwnd, &window_rect))
return true;
// We are interested in this window, but are not currently hooking it with
// EVENT_OBJECT_LOCATION_CHANGE, so we need to hook it. We check
// this by seeing if its PID is in |process_event_hooks_|.
DWORD pid;
GetWindowThreadProcessId(hwnd, &pid);
current_pids_with_visible_windows->insert(pid);
if (!base::ContainsKey(process_event_hooks_, pid))
RegisterEventHookForProcess(pid);
SkRegion window_region(SkIRect::MakeLTRB(window_rect.x(), window_rect.y(),
window_rect.right(),
window_rect.bottom()));
for (auto& root_window_pair : root_window_hwnds_occlusion_state_) {
if (root_window_pair.second.occlusion_state !=
Window::OcclusionState::UNKNOWN)
continue;
if (!root_window_pair.second.unoccluded_region.op(
window_region, SkRegion::kDifference_Op)) {
root_window_pair.second.occlusion_state =
Window::OcclusionState::OCCLUDED;
}
}
return true;
}
void NativeWindowOcclusionTrackerWin::WindowOcclusionCalculator::
ProcessEventHookCallback(DWORD event,
HWND hwnd,
LONG idObject,
LONG idChild) {
// Can't do DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_) here. See
// comment before call to PostTask below as to why.
// No need to calculate occlusion if a zero HWND generated the event. This
// happens if there is no window associated with the event, e.g., mouse move
// events.
if (!hwnd)
return;
// 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) {
// TODO(davidbienvenu): convert to DCHECK once we've confirmed in canary
// that this condition isn't met.
CHECK(!window_is_moving_);
window_is_moving_ = true;
} else if (event == EVENT_SYSTEM_MOVESIZEEND) {
window_is_moving_ = false;
} else if (window_is_moving_) {
return;
}
// ProcessEventHookCallback is called from the task_runner's PeekMessage
// call, on the task runner's thread, but before the task_tracker thread sets
// up the thread sequence. In order to prevent DCHECK failures with the
// |occlusion_update_timer_, we need to call
// ScheduleOcclusionCalculationIfNeeded from a task.
// See SchedulerWorkerCOMDelegate::GetWorkFromWindowsMessageQueue().
task_runner_->PostTask(
FROM_HERE,
base::BindOnce(
&WindowOcclusionCalculator::ScheduleOcclusionCalculationIfNeeded,
base::Unretained(this)));
}
void NativeWindowOcclusionTrackerWin::WindowOcclusionCalculator::
ProcessUpdateVisibleWindowProcessIdsCallback(HWND hwnd) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
gfx::Rect window_rect;
if (IsWindowVisibleAndFullyOpaque(hwnd, &window_rect)) {
DWORD pid;
GetWindowThreadProcessId(hwnd, &pid);
pids_for_location_change_hook_.insert(pid);
}
}
} // namespace aura
// Copyright 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef UI_AURA_NATIVE_WINDOW_OCCLUSION_TRACKER_WIN_H_
#define UI_AURA_NATIVE_WINDOW_OCCLUSION_TRACKER_WIN_H_
#include <windows.h>
#include <winuser.h>
#include <vector>
#include "base/containers/flat_map.h"
#include "base/containers/flat_set.h"
#include "base/sequenced_task_runner.h"
#include "base/time/time.h"
#include "ui/aura/aura_export.h"
#include "ui/aura/window.h"
#include "ui/aura/window_observer.h"
namespace aura {
// This class keeps track of whether any HWNDs are occluding any app windows.
// It notifies the host of any app window whose occlusion state changes. Most
// code should not need to use this; it's an implementation detail.
class AURA_EXPORT NativeWindowOcclusionTrackerWin : public WindowObserver {
public:
static NativeWindowOcclusionTrackerWin* GetOrCreateInstance();
// Enables notifying the host of |window| via SetNativeWindowOcclusionState()
// when the occlusion state has been computed.
void Enable(Window* window);
// Disables notifying the host of |window| via
// OnNativeWindowOcclusionStateChanged() when the occlusion state has been
// computed. It's not neccesary to call this when |window| is deleted because
// OnWindowDestroying calls Disable.
void Disable(Window* window);
// aura::WindowObserver:
void OnWindowVisibilityChanged(Window* window, bool visible) override;
void OnWindowDestroying(Window* window) override;
private:
friend class NativeWindowOcclusionTrackerTest;
// This class computes the occlusion state of the tracked windows.
// It runs on a separate thread, and notifies the main thread of
// the occlusion state of the tracked windows.
class WindowOcclusionCalculator {
public:
WindowOcclusionCalculator(
scoped_refptr<base::SequencedTaskRunner> task_runner,
scoped_refptr<base::SequencedTaskRunner> ui_thread_task_runner);
~WindowOcclusionCalculator();
void EnableOcclusionTrackingForWindow(HWND hwnd);
void DisableOcclusionTrackingForWindow(HWND hwnd);
// If a window becomes visible, makes sure event hooks are registered.
void HandleVisibilityChanged(bool visible);
private:
friend class NativeWindowOcclusionTrackerTest;
struct NativeWindowOcclusionState {
// The region of the native window that is not occluded by other windows.
SkRegion unoccluded_region;
// The current occlusion state of the native window. Default to UNKNOWN
// because we do not know the state starting out. More information on
// these states can be found in aura::Window.
aura::Window::OcclusionState occlusion_state =
aura::Window::OcclusionState::UNKNOWN;
};
// Registers event hooks, if not registered.
void MaybeRegisterEventHooks();
// This is the callback registered to get notified of various Windows
// events, like window moving/resizing.
static void CALLBACK EventHookCallback(HWINEVENTHOOK hWinEventHook,
DWORD event,
HWND hwnd,
LONG idObject,
LONG idChild,
DWORD dwEventThread,
DWORD dwmsEventTime);
// EnumWindows callback used to iterate over all hwnds to determine
// occlusion status of all tracked root windows. Also builds up
// |current_pids_with_visible_windows_| and registers event hooks for newly
// discovered processes with visible hwnds.
static BOOL CALLBACK
ComputeNativeWindowOcclusionStatusCallback(HWND hwnd, LPARAM lParam);
// EnumWindows callback used to update the list of process ids with
// visible hwnds, |pids_for_location_change_hook_|.
static BOOL CALLBACK UpdateVisibleWindowProcessIdsCallback(HWND hwnd,
LPARAM lParam);
// Determines which processes owning visible application windows to set the
// EVENT_OBJECT_LOCATIONCHANGE event hook for and stores the pids in
// |pids_for_location_change_hook_|.
void UpdateVisibleWindowProcessIds();
// Computes the native window occlusion status for all tracked root aura
// windows in |root_window_hwnds_occlusion_state_| and notifies them if
// their occlusion status has changed.
void ComputeNativeWindowOcclusionStatus();
// Schedules an occlusion calculation |update_occlusion_delay_| time in the
// future, if one isn't already scheduled.
void ScheduleOcclusionCalculationIfNeeded();
// Registers a global event hook (not per process) for the events in the
// range from |event_min| to |event_max|, inclusive.
void RegisterGlobalEventHook(UINT event_min, UINT event_max);
// Registers the EVENT_OBJECT_LOCATIONCHANGE event hook for the process with
// passed id. The process has one or more visible, opaque windows.
void RegisterEventHookForProcess(DWORD pid);
// Registers/Unregisters the event hooks necessary for occlusion tracking
// via calls to RegisterEventHook. These event hooks are disabled when all
// tracked windows are minimized.
void RegisterEventHooks();
void UnregisterEventHooks();
// EnumWindows callback for occlusion calculation. Returns true to
// continue enumeration, false otherwise. Currently, always returns
// true because this function also updates
// |current_pids_with_visible_windows|, and needs to see all HWNDs.
bool ProcessComputeNativeWindowOcclusionStatusCallback(
HWND hwnd,
base::flat_set<DWORD>* current_pids_with_visible_windows);
// Processes events sent to OcclusionEventHookCallback.
// It generally triggers scheduling of the occlusion calculation, but
// ignores certain events in order to not calculate occlusion more than
// necessary.
void ProcessEventHookCallback(DWORD event,
HWND hwnd,
LONG idObject,
LONG idChild);
// EnumWindows callback for determining which processes to set the
// EVENT_OBJECT_LOCATIONCHANGE event hook for. We set that event hook for
// processes hosting fully visible, opaque windows.
void ProcessUpdateVisibleWindowProcessIdsCallback(HWND hwnd);
// Task runner for our thread.
scoped_refptr<base::SequencedTaskRunner> task_runner_;
// Task runner for the thread that created |this|. UpdateOcclusionState
// task is posted to this task runner.
const scoped_refptr<base::SequencedTaskRunner> ui_thread_task_runner_;
// Map of root app window hwnds and their occlusion state. This contains
// both visible and hidden windows.
base::flat_map<HWND, NativeWindowOcclusionState>
root_window_hwnds_occlusion_state_;
// Values returned by SetWinEventHook are stored so that hooks can be
// unregistered when necessary.
std::vector<HWINEVENTHOOK> global_event_hooks_;
// Map from process id to EVENT_OBJECT_LOCATIONCHANGE event hook.
base::flat_map<DWORD, HWINEVENTHOOK> process_event_hooks_;
// Pids of processes for which the EVENT_OBJECT_LOCATIONCHANGE event hook is
// set. These are the processes hosting windows in
// |visible_and_fully_opaque_windows_|.
base::flat_set<DWORD> pids_for_location_change_hook_;
// Timer to delay occlusion update.
base::OneShotTimer occlusion_update_timer_;
// Used to keep track of whether we're in the middle of getting window move
// events, in order to wait until the window move is complete before
// calculating window occlusion.
bool window_is_moving_ = false;
SEQUENCE_CHECKER(sequence_checker_);
DISALLOW_COPY_AND_ASSIGN(WindowOcclusionCalculator);
};
NativeWindowOcclusionTrackerWin();
~NativeWindowOcclusionTrackerWin() override;
// Returns true if we are interested in |hwnd| for purposes of occlusion
// calculation. We are interested in |hwnd| if it is a window that is visible,
// opaque, and bounded. If we are interested in |hwnd|, stores the window
// rectangle in |window_rect|.
static bool IsWindowVisibleAndFullyOpaque(HWND hwnd, gfx::Rect* window_rect);
// Updates root windows occclusion state.
void UpdateOcclusionState(const base::flat_map<HWND, Window::OcclusionState>&
root_window_hwnds_occlusion_state);
// Task runner to call ComputeNativeWindowOcclusionStatus, and to handle
// Windows event notifications, off of the UI thread.
const scoped_refptr<base::SequencedTaskRunner> update_occlusion_task_runner_;
// Map of HWND to root app windows. Maintained on the UI thread, and used
// to send occlusion state notifications to Windows from
// |root_window_hwnds_occlusion_state_|.
base::flat_map<HWND, Window*> hwnd_root_window_map_;
std::unique_ptr<WindowOcclusionCalculator> occlusion_calculator_;
DISALLOW_COPY_AND_ASSIGN(NativeWindowOcclusionTrackerWin);
};
} // namespace aura
#endif // UI_AURA_NATIVE_WINDOW_OCCLUSION_TRACKER_WIN_H_
// Copyright 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "ui/aura/native_window_occlusion_tracker_win.h"
#include <winuser.h>
#include "base/at_exit.h"
#include "base/command_line.h"
#include "base/feature_list.h"
#include "base/macros.h"
#include "base/run_loop.h"
#include "base/task/task_scheduler/task_scheduler.h"
#include "base/test/scoped_feature_list.h"
#include "base/win/scoped_gdi_object.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "ui/aura/env.h"
#include "ui/aura/test/aura_test_base.h"
#include "ui/aura/test/test_focus_client.h"
#include "ui/aura/test/test_screen.h"
#include "ui/aura/test/test_window_delegate.h"
#include "ui/aura/test/test_window_parenting_client.h"
#include "ui/aura/window.h"
#include "ui/aura/window_occlusion_tracker.h"
#include "ui/aura/window_tree_host.h"
#include "ui/aura/window_tree_host_platform.h"
#include "ui/base/ime/input_method_initializer.h"
#include "ui/base/ui_base_features.h"
#include "ui/display/win/dpi.h"
#include "ui/gfx/geometry/rect.h"
#include "ui/gfx/win/window_impl.h"
#include "ui/gl/test/gl_surface_test_support.h"
namespace aura {
// This class is used to verify expectations about occlusion state changes by
// adding instances of it as an observer of aura:Windows the tests create and
// checking that they get the expected call(s) to OnOcclusionStateChanged.
// The tests verify that the current state, when idle, is the expected state,
// because the state can be VISIBLE before it reaches the expected state.
class MockWindowTreeHostObserver : public WindowTreeHostObserver {
public:
MockWindowTreeHostObserver() {}
~MockWindowTreeHostObserver() override { EXPECT_FALSE(is_expecting_call()); }
// WindowTreeHostObserver:
void OnOcclusionStateChanged(WindowTreeHost* host,
Window::OcclusionState new_state) override {
EXPECT_NE(new_state, Window::OcclusionState::UNKNOWN);
// Should only get notified when the occlusion state changes.
EXPECT_NE(new_state, cur_state_);
cur_state_ = new_state;
}
void set_expectation(Window::OcclusionState expectation) {
expectation_ = expectation;
}
bool is_expecting_call() const { return expectation_ != cur_state_; }
private:
Window::OcclusionState expectation_ = Window::OcclusionState::UNKNOWN;
Window::OcclusionState cur_state_ = Window::OcclusionState::UNKNOWN;
DISALLOW_COPY_AND_ASSIGN(MockWindowTreeHostObserver);
};
// Test wrapper around native window HWND.
class TestNativeWindow : public gfx::WindowImpl {
public:
TestNativeWindow() {}
~TestNativeWindow() override;
private:
// Overridden from gfx::WindowImpl:
BOOL ProcessWindowMessage(HWND window,
UINT message,
WPARAM w_param,
LPARAM l_param,
LRESULT& result,
DWORD msg_map_id) override {
return FALSE; // Results in DefWindowProc().
}
DISALLOW_COPY_AND_ASSIGN(TestNativeWindow);
};
TestNativeWindow::~TestNativeWindow() {
if (hwnd())
DestroyWindow(hwnd());
}
class NativeWindowOcclusionTrackerTest : public test::AuraTestBase {
public:
NativeWindowOcclusionTrackerTest() {}
void SetUp() override {
if (gl::GetGLImplementation() == gl::kGLImplementationNone)
gl::GLSurfaceTestSupport::InitializeOneOff();
AuraTestBase::SetUp();
ui::InitializeInputMethodForTesting();
display::Screen::SetScreenInstance(test_screen());
scoped_feature_list_.InitAndEnableFeature(
features::kCalculateNativeWinOcclusion);
}
void SetNativeWindowBounds(HWND hwnd, const gfx::Rect& bounds) {
RECT wr = bounds.ToRECT();
AdjustWindowRectEx(&wr, GetWindowLong(hwnd, GWL_STYLE), FALSE,
GetWindowLong(hwnd, GWL_EXSTYLE));
// Make sure to keep the window onscreen, as AdjustWindowRectEx() may have
// moved part of it offscreen.
gfx::Rect window_bounds(wr);
window_bounds.set_x(std::max(0, window_bounds.x()));
window_bounds.set_y(std::max(0, window_bounds.y()));
SetWindowPos(hwnd, HWND_TOP, window_bounds.x(), window_bounds.y(),
window_bounds.width(), window_bounds.height(),
SWP_NOREPOSITION);
EXPECT_TRUE(UpdateWindow(hwnd));
}
HWND CreateNativeWindowWithBounds(const gfx::Rect& bounds) {
native_win_ = std::make_unique<TestNativeWindow>();
native_win_->set_window_style(WS_OVERLAPPEDWINDOW | WS_CLIPCHILDREN);
native_win_->Init(nullptr, bounds);
HWND hwnd = native_win_->hwnd();
SetNativeWindowBounds(hwnd, bounds);
base::win::ScopedRegion region(CreateRectRgn(0, 0, 0, 0));
if (GetWindowRgn(hwnd, region.get()) == COMPLEXREGION) {
// On Windows 7, the newly created window has a complex region, which
// means it will be ignored during the occlusion calculation. So, force
// it to have a simple region so that we get test coverage on win 7.
RECT bounding_rect;
GetWindowRect(hwnd, &bounding_rect);
base::win::ScopedRegion rectangular_region(
CreateRectRgnIndirect(&bounding_rect));
SetWindowRgn(hwnd, rectangular_region.get(), TRUE);
}
ShowWindow(hwnd, SW_SHOWNORMAL);
EXPECT_TRUE(UpdateWindow(hwnd));
return hwnd;
}
void CreateTrackedAuraWindowWithBounds(MockWindowTreeHostObserver* observer,
gfx::Rect bounds) {
host()->Show();
host()->SetBoundsInPixels(bounds);
host()->AddObserver(observer);
Window* window = CreateNormalWindow(1, host()->window(), nullptr);
window->SetBounds(bounds);
window->env()->GetWindowOcclusionTracker()->Track(window);
}
private:
base::test::ScopedFeatureList scoped_feature_list_;
std::unique_ptr<TestNativeWindow> native_win_;
DISALLOW_COPY_AND_ASSIGN(NativeWindowOcclusionTrackerTest);
};
// Simple test completely covering an aura window with a native window.
TEST_F(NativeWindowOcclusionTrackerTest, SimpleOcclusion) {
base::RunLoop run_loop;
std::unique_ptr<MockWindowTreeHostObserver> observer =
std::make_unique<MockWindowTreeHostObserver>();
CreateTrackedAuraWindowWithBounds(observer.get(), gfx::Rect(0, 0, 100, 100));
observer->set_expectation(Window::OcclusionState::OCCLUDED);
CreateNativeWindowWithBounds(gfx::Rect(0, 0, 100, 100));
run_loop.RunUntilIdle();
EXPECT_FALSE(observer->is_expecting_call());
}
// Simple test with an aura window and native window that do not overlap.
TEST_F(NativeWindowOcclusionTrackerTest, SimpleVisible) {
base::RunLoop run_loop;
MockWindowTreeHostObserver observer;
CreateTrackedAuraWindowWithBounds(&observer, gfx::Rect(0, 0, 100, 100));
observer.set_expectation(Window::OcclusionState::VISIBLE);
CreateNativeWindowWithBounds(gfx::Rect(200, 0, 100, 100));
run_loop.RunUntilIdle();
EXPECT_FALSE(observer.is_expecting_call());
}
// Simple test with a minimized aura window and native window.
TEST_F(NativeWindowOcclusionTrackerTest, SimpleHidden) {
base::RunLoop run_loop;
MockWindowTreeHostObserver observer;
CreateTrackedAuraWindowWithBounds(&observer, gfx::Rect(0, 0, 100, 100));
CreateNativeWindowWithBounds(gfx::Rect(200, 0, 100, 100));
// Minimize the tracked aura window and check that its occlusion state
// is HIDDEN.
::ShowWindow(host()->GetAcceleratedWidget(), SW_MINIMIZE);
observer.set_expectation(Window::OcclusionState::HIDDEN);
run_loop.RunUntilIdle();
EXPECT_FALSE(observer.is_expecting_call());
}
} // namespace aura
......@@ -11,6 +11,7 @@
#include "third_party/skia/include/core/SkRegion.h"
#include "ui/aura/env.h"
#include "ui/aura/window_tracker.h"
#include "ui/aura/window_tree_host.h"
#include "ui/gfx/geometry/safe_integer_conversions.h"
#include "ui/gfx/transform.h"
......@@ -430,9 +431,18 @@ void WindowOcclusionTracker::TrackedWindowAddedToRoot(Window* window) {
DCHECK(root_window);
RootWindowState& root_window_state = root_windows_[root_window];
++root_window_state.num_tracked_windows;
if (root_window_state.num_tracked_windows == 1)
AddObserverToWindowAndDescendants(root_window);
MarkRootWindowAsDirty(&root_window_state);
// It's only useful to track the host if |window| is the first tracked window
// under |root_window|. All windows under the same root have the same host.
if (root_window_state.num_tracked_windows == 1) {
AddObserverToWindowAndDescendants(root_window);
auto* host = root_window->GetHost();
if (host) {
host->AddObserver(this);
host->EnableNativeWindowOcclusionTracking();
}
}
MaybeComputeOcclusion();
}
......@@ -445,6 +455,8 @@ void WindowOcclusionTracker::TrackedWindowRemovedFromRoot(Window* window) {
if (root_window_state_it->second.num_tracked_windows == 0) {
RemoveObserverFromWindowAndDescendants(root_window);
root_windows_.erase(root_window_state_it);
root_window->GetHost()->RemoveObserver(this);
root_window->GetHost()->DisableNativeWindowOcclusionTracking();
}
}
......@@ -631,4 +643,8 @@ void WindowOcclusionTracker::OnWindowLayerRecreated(Window* window) {
}
}
void WindowOcclusionTracker::OnOcclusionStateChanged(
WindowTreeHost* host,
aura::Window::OcclusionState new_state) {}
} // namespace aura
......@@ -16,6 +16,7 @@
#include "ui/aura/aura_export.h"
#include "ui/aura/window.h"
#include "ui/aura/window_observer.h"
#include "ui/aura/window_tree_host_observer.h"
#include "ui/compositor/layer_animation_observer.h"
struct SkIRect;
......@@ -46,7 +47,8 @@ class Env;
// Note that an occluded window may be drawn on the screen by window switching
// features such as "Alt-Tab" or "Overview".
class AURA_EXPORT WindowOcclusionTracker : public ui::LayerAnimationObserver,
public WindowObserver {
public WindowObserver,
public WindowTreeHostObserver {
public:
// Prevents window occlusion state computations within its scope. If an event
// that could cause window occlusion states to change occurs within the scope
......@@ -214,6 +216,10 @@ class AURA_EXPORT WindowOcclusionTracker : public ui::LayerAnimationObserver,
Window* new_root) override;
void OnWindowLayerRecreated(Window* window) override;
// WindowTreeHostObserver
void OnOcclusionStateChanged(WindowTreeHost* host,
Window::OcclusionState new_state) override;
// Windows whose occlusion state is tracked.
base::flat_map<Window*, Window::OcclusionState> tracked_windows_;
......
......@@ -9,6 +9,7 @@
#include "base/metrics/histogram_macros.h"
#include "base/threading/thread_task_runner_handle.h"
#include "base/trace_event/trace_event.h"
#include "build/build_config.h"
#include "components/viz/common/features.h"
#include "ui/aura/client/capture_client.h"
#include "ui/aura/client/cursor_client.h"
......@@ -39,6 +40,10 @@
#include "ui/gfx/icc_profile.h"
#include "ui/platform_window/platform_window_init_properties.h"
#if defined(OS_WIN)
#include "ui/aura/native_window_occlusion_tracker_win.h"
#endif // OS_WIN
namespace aura {
namespace {
......@@ -78,6 +83,12 @@ class ScopedLocalSurfaceIdValidator {
};
#endif
#if defined(OS_WIN)
bool IsNativeWindowOcclusionEnabled() {
return base::FeatureList::IsEnabled(features::kCalculateNativeWinOcclusion);
}
#endif // OS_WIN
} // namespace
////////////////////////////////////////////////////////////////////////////////
......@@ -118,6 +129,10 @@ void WindowTreeHost::RemoveObserver(WindowTreeHostObserver* observer) {
observers_.RemoveObserver(observer);
}
bool WindowTreeHost::HasObserver(const WindowTreeHostObserver* observer) const {
return observers_.HasObserver(observer);
}
ui::EventSink* WindowTreeHost::event_sink() {
return dispatcher_.get();
}
......@@ -292,11 +307,38 @@ bool WindowTreeHost::ShouldSendKeyEventToIme() {
return true;
}
void WindowTreeHost::EnableNativeWindowOcclusionTracking() {
#if defined(OS_WIN)
if (IsNativeWindowOcclusionEnabled()) {
NativeWindowOcclusionTrackerWin::GetOrCreateInstance()->Enable(window());
}
#endif // OS_WIN
}
void WindowTreeHost::DisableNativeWindowOcclusionTracking() {
#if defined(OS_WIN)
if (IsNativeWindowOcclusionEnabled()) {
occlusion_state_ = Window::OcclusionState::UNKNOWN;
NativeWindowOcclusionTrackerWin::GetOrCreateInstance()->Disable(window());
}
#endif // OS_WIN
}
void WindowTreeHost::SetNativeWindowOcclusionState(
Window::OcclusionState state) {
if (occlusion_state_ != state) {
occlusion_state_ = state;
for (WindowTreeHostObserver& observer : observers_)
observer.OnOcclusionStateChanged(this, state);
}
}
////////////////////////////////////////////////////////////////////////////////
// WindowTreeHost, protected:
WindowTreeHost::WindowTreeHost(std::unique_ptr<Window> window)
: window_(window.release()), // See header for details on ownership.
occlusion_state_(Window::OcclusionState::UNKNOWN),
last_cursor_(ui::CursorType::kNull),
input_method_(nullptr),
owned_input_method_(false),
......
......@@ -80,6 +80,7 @@ class AURA_EXPORT WindowTreeHost : public ui::internal::InputMethodDelegate,
void AddObserver(WindowTreeHostObserver* observer);
void RemoveObserver(WindowTreeHostObserver* observer);
bool HasObserver(const WindowTreeHostObserver* observer) const;
Window* window() { return window_; }
const Window* window() const { return window_; }
......@@ -229,6 +230,18 @@ class AURA_EXPORT WindowTreeHost : public ui::internal::InputMethodDelegate,
// WindowEventDispatcher during event dispatch.
virtual bool ShouldSendKeyEventToIme();
// Enables native window occlusion tracking for the native window this host
// represents.
virtual void EnableNativeWindowOcclusionTracking();
// Disables native window occlusion tracking for the native window this host
// represents.
virtual void DisableNativeWindowOcclusionTracking();
// Remembers the current occlusion state, and if it has changed, notifies
// observers of the change.
virtual void SetNativeWindowOcclusionState(Window::OcclusionState state);
protected:
friend class ScopedKeyboardHook;
friend class TestScreen; // TODO(beng): see if we can remove/consolidate.
......@@ -328,6 +341,10 @@ class AURA_EXPORT WindowTreeHost : public ui::internal::InputMethodDelegate,
// the end of the dtor).
Window* window_; // Owning.
// Keeps track of the occlusion state of the host, and used to send
// notifications to observers when it changes.
Window::OcclusionState occlusion_state_;
base::ObserverList<WindowTreeHostObserver>::Unchecked observers_;
std::unique_ptr<WindowEventDispatcher> dispatcher_;
......
......@@ -6,6 +6,7 @@
#define UI_AURA_WINDOW_TREE_HOST_OBSERVER_H_
#include "ui/aura/aura_export.h"
#include "ui/aura/window.h"
namespace gfx {
class Point;
......@@ -29,6 +30,11 @@ class AURA_EXPORT WindowTreeHostObserver {
// Called when the native window system sends the host request to close.
virtual void OnHostCloseRequested(WindowTreeHost* host) {}
// Called when the occlusion status of the native window changes, iff
// occlusion tracking is enabled for a descendant of the root.
virtual void OnOcclusionStateChanged(WindowTreeHost* host,
Window::OcclusionState new_state) {}
protected:
virtual ~WindowTreeHostObserver() {}
};
......
......@@ -12,6 +12,11 @@
namespace features {
#if defined(OS_WIN)
// If enabled, calculate native window occlusion - Windows-only.
const base::Feature kCalculateNativeWinOcclusion{
"CalculateNativeWinOcclusion", base::FEATURE_DISABLED_BY_DEFAULT};
#endif // OW_WIN
// If enabled, the emoji picker context menu item may be shown for editable
// text areas.
const base::Feature kEnableEmojiContextMenu {
......
......@@ -32,6 +32,7 @@ UI_BASE_EXPORT bool IsNotificationIndicatorEnabled();
UI_BASE_EXPORT bool IsUiGpuRasterizationEnabled();
#if defined(OS_WIN)
UI_BASE_EXPORT extern const base::Feature kCalculateNativeWinOcclusion;
UI_BASE_EXPORT extern const base::Feature kInputPaneOnScreenKeyboard;
UI_BASE_EXPORT extern const base::Feature kPointerEventsForTouch;
UI_BASE_EXPORT extern const base::Feature kPrecisionTouchpad;
......
......@@ -15,6 +15,7 @@
#include "ui/base/class_property.h"
#include "ui/base/cursor/cursor_loader_win.h"
#include "ui/base/ime/input_method.h"
#include "ui/base/ui_base_features.h"
#include "ui/base/win/shell.h"
#include "ui/compositor/paint_context.h"
#include "ui/display/win/dpi.h"
......@@ -28,6 +29,7 @@
#include "ui/gfx/path.h"
#include "ui/gfx/path_win.h"
#include "ui/views/corewm/tooltip_win.h"
#include "ui/views/views_switches.h"
#include "ui/views/widget/desktop_aura/desktop_drag_drop_client_win.h"
#include "ui/views/widget/desktop_aura/desktop_native_cursor_manager.h"
#include "ui/views/widget/desktop_aura/desktop_native_widget_aura.h"
......@@ -175,7 +177,6 @@ DesktopWindowTreeHostWin::CreateDragDropClient(
void DesktopWindowTreeHostWin::Close() {
content_window()->Hide();
// TODO(beng): Move this entire branch to DNWA so it can be shared with X11.
if (should_animate_window_close_) {
pending_close_ = true;
......@@ -880,6 +881,9 @@ void DesktopWindowTreeHostWin::HandleNativeBlur(HWND focused_window) {
}
bool DesktopWindowTreeHostWin::HandleMouseEvent(ui::MouseEvent* event) {
// TODO(davidbienvenu): Check for getting mouse events for an occluded window
// with either a DCHECK or a stat. Event can cause this object to be deleted
// so look at occlusion state before we do anything with the event.
SendEventToSink(event);
return event->handled();
}
......
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