Reland "Native window occlusion tracking for windows."
This is a reland of 03b56190ff55b876dc1b4a1398930154240a3170
Original change's description:
> 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 <[email protected]>
> Reviewed-by: Scott Violet <[email protected]>
> Reviewed-by: François Doray <[email protected]>
> Cr-Commit-Position: refs/heads/master@{#603619}
Bug: 813093
Change-Id: I78fa7b04a7efc0c392100b852e1a1a5f04421132
Reviewed-on: https://chromium-review.googlesource.com/c/1308264
Commit-Queue: David Bienvenu <[email protected]>
Reviewed-by: Scott Violet <[email protected]>
Cr-Commit-Position: refs/heads/master@{#604618}
diff --git a/chrome/browser/about_flags.cc b/chrome/browser/about_flags.cc
index a16e2b0..45046da7 100644
--- a/chrome/browser/about_flags.cc
+++ b/chrome/browser/about_flags.cc
@@ -4326,6 +4326,12 @@
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",
diff --git a/chrome/browser/flag_descriptions.cc b/chrome/browser/flag_descriptions.cc
index e909842f..de6fb7b 100644
--- a/chrome/browser/flag_descriptions.cc
+++ b/chrome/browser/flag_descriptions.cc
@@ -2859,6 +2859,12 @@
#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 "
diff --git a/chrome/browser/flag_descriptions.h b/chrome/browser/flag_descriptions.h
index d77375a..32c61e8a 100644
--- a/chrome/browser/flag_descriptions.h
+++ b/chrome/browser/flag_descriptions.h
@@ -1717,6 +1717,9 @@
#if defined(OS_WIN)
+extern const char kCalculateNativeWinOcclusionName[];
+extern const char kCalculateNativeWinOcclusionDescription[];
+
extern const char kCloudPrintXpsName[];
extern const char kCloudPrintXpsDescription[];
diff --git a/chrome/test/BUILD.gn b/chrome/test/BUILD.gn
index 4f8eb39..fb9874f 100644
--- a/chrome/test/BUILD.gn
+++ b/chrome/test/BUILD.gn
@@ -4796,6 +4796,7 @@
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",
diff --git a/tools/metrics/histograms/enums.xml b/tools/metrics/histograms/enums.xml
index 4ded5db..7ddb939 100644
--- a/tools/metrics/histograms/enums.xml
+++ b/tools/metrics/histograms/enums.xml
@@ -29212,6 +29212,7 @@
<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"/>
@@ -31112,6 +31113,7 @@
<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"/>
diff --git a/ui/aura/BUILD.gn b/ui/aura/BUILD.gn
index 1b39f76..45635fc 100644
--- a/ui/aura/BUILD.gn
+++ b/ui/aura/BUILD.gn
@@ -138,6 +138,8 @@
"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 @@
"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 @@
"window_unittest.cc",
]
+ if (is_win) {
+ sources += [ "native_window_occlusion_tracker_unittest.cc" ]
+ }
+
deps = [
":test_support",
"//base/test:test_support",
@@ -424,3 +436,29 @@
"//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",
+ ]
+ }
+}
diff --git a/ui/aura/DEPS b/ui/aura/DEPS
index 263ea23..b2ad0cd2 100644
--- a/ui/aura/DEPS
+++ b/ui/aura/DEPS
@@ -20,6 +20,7 @@
"+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",
diff --git a/ui/aura/native_window_occlusion_tracker_unittest.cc b/ui/aura/native_window_occlusion_tracker_unittest.cc
new file mode 100644
index 0000000..f34b36a
--- /dev/null
+++ b/ui/aura/native_window_occlusion_tracker_unittest.cc
@@ -0,0 +1,158 @@
+// 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
diff --git a/ui/aura/native_window_occlusion_tracker_win.cc b/ui/aura/native_window_occlusion_tracker_win.cc
new file mode 100644
index 0000000..0dddd3d
--- /dev/null
+++ b/ui/aura/native_window_occlusion_tracker_win.cc
@@ -0,0 +1,536 @@
+// 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>(¤t_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
diff --git a/ui/aura/native_window_occlusion_tracker_win.h b/ui/aura/native_window_occlusion_tracker_win.h
new file mode 100644
index 0000000..477e77a
--- /dev/null
+++ b/ui/aura/native_window_occlusion_tracker_win.h
@@ -0,0 +1,217 @@
+// 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_
diff --git a/ui/aura/native_window_occlusion_tracker_win_interactive_test.cc b/ui/aura/native_window_occlusion_tracker_win_interactive_test.cc
new file mode 100644
index 0000000..674a2a90
--- /dev/null
+++ b/ui/aura/native_window_occlusion_tracker_win_interactive_test.cc
@@ -0,0 +1,211 @@
+// 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:
+ explicit MockWindowTreeHostObserver(base::OnceClosure quit_closure)
+ : quit_closure_(std::move(quit_closure)) {}
+ ~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;
+ if (cur_state_ == expectation_) {
+ EXPECT_FALSE(quit_closure_.is_null());
+ std::move(quit_closure_).Run();
+ }
+ }
+
+ 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;
+ base::OnceClosure quit_closure_;
+
+ 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;
+
+ MockWindowTreeHostObserver observer(run_loop.QuitClosure());
+ CreateTrackedAuraWindowWithBounds(&observer, gfx::Rect(0, 0, 100, 100));
+ observer.set_expectation(Window::OcclusionState::OCCLUDED);
+ CreateNativeWindowWithBounds(gfx::Rect(0, 0, 100, 100));
+ run_loop.Run();
+ 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(run_loop.QuitClosure());
+ CreateTrackedAuraWindowWithBounds(&observer, gfx::Rect(0, 0, 100, 100));
+ observer.set_expectation(Window::OcclusionState::VISIBLE);
+ CreateNativeWindowWithBounds(gfx::Rect(200, 0, 100, 100));
+
+ run_loop.Run();
+ 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(run_loop.QuitClosure());
+ 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.Run();
+ EXPECT_FALSE(observer.is_expecting_call());
+}
+
+} // namespace aura
diff --git a/ui/aura/window_occlusion_tracker.cc b/ui/aura/window_occlusion_tracker.cc
index 842b586f..810a5d3 100644
--- a/ui/aura/window_occlusion_tracker.cc
+++ b/ui/aura/window_occlusion_tracker.cc
@@ -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"
@@ -445,9 +446,18 @@
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();
}
@@ -460,6 +470,8 @@
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();
}
}
@@ -646,4 +658,8 @@
}
}
+void WindowOcclusionTracker::OnOcclusionStateChanged(
+ WindowTreeHost* host,
+ aura::Window::OcclusionState new_state) {}
+
} // namespace aura
diff --git a/ui/aura/window_occlusion_tracker.h b/ui/aura/window_occlusion_tracker.h
index 8712c6f6..eb73dd4f 100644
--- a/ui/aura/window_occlusion_tracker.h
+++ b/ui/aura/window_occlusion_tracker.h
@@ -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 @@
// 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
@@ -232,6 +234,9 @@
// Windows whose occlusion data is tracked.
base::flat_map<Window*, OcclusionData> tracked_windows_;
+ // WindowTreeHostObserver
+ void OnOcclusionStateChanged(WindowTreeHost* host,
+ Window::OcclusionState new_state) override;
// Windows whose bounds or transform are animated.
//
diff --git a/ui/aura/window_tree_host.cc b/ui/aura/window_tree_host.cc
index f6d5c61..1379440 100644
--- a/ui/aura/window_tree_host.cc
+++ b/ui/aura/window_tree_host.cc
@@ -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 @@
};
#endif
+#if defined(OS_WIN)
+bool IsNativeWindowOcclusionEnabled() {
+ return base::FeatureList::IsEnabled(features::kCalculateNativeWinOcclusion);
+}
+#endif // OS_WIN
+
} // namespace
////////////////////////////////////////////////////////////////////////////////
@@ -118,6 +129,10 @@
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 @@
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),
diff --git a/ui/aura/window_tree_host.h b/ui/aura/window_tree_host.h
index 7e19ae7..50b18ca 100644
--- a/ui/aura/window_tree_host.h
+++ b/ui/aura/window_tree_host.h
@@ -80,6 +80,7 @@
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 @@
// 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 @@
// 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_;
diff --git a/ui/aura/window_tree_host_observer.h b/ui/aura/window_tree_host_observer.h
index e71455dc..00fe3b4 100644
--- a/ui/aura/window_tree_host_observer.h
+++ b/ui/aura/window_tree_host_observer.h
@@ -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 @@
// 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() {}
};
diff --git a/ui/base/ui_base_features.cc b/ui/base/ui_base_features.cc
index 674998b..98700d7f 100644
--- a/ui/base/ui_base_features.cc
+++ b/ui/base/ui_base_features.cc
@@ -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 {
diff --git a/ui/base/ui_base_features.h b/ui/base/ui_base_features.h
index 4a799dda3..a6c9a2c 100644
--- a/ui/base/ui_base_features.h
+++ b/ui/base/ui_base_features.h
@@ -32,6 +32,7 @@
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;
diff --git a/ui/views/widget/desktop_aura/desktop_window_tree_host_win.cc b/ui/views/widget/desktop_aura/desktop_window_tree_host_win.cc
index 0886f20..a039a7a 100644
--- a/ui/views/widget/desktop_aura/desktop_window_tree_host_win.cc
+++ b/ui/views/widget/desktop_aura/desktop_window_tree_host_win.cc
@@ -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 @@
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 @@
}
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();
}