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>(&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
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();
 }