Tests for WindowFinder

This change adds several tests for WindowFinder implmenetations.
One of the tests uses ScreenDrawer to create a foreground window or console
window (on Windows) and ensures WindowFinder can return its window_id().
The other ensures WindowFinder always returns kNullWindowId if the |pos| is out
of the screens.

Bug: webrtc:7950
Change-Id: I0cc8794e201c2fa042ea8e693434f1b0fa05b47a
Reviewed-on: https://chromium-review.googlesource.com/639591
Reviewed-by: Jamie Walch <jamiewalch@chromium.org>
Commit-Queue: Zijie He <zijiehe@chromium.org>
Cr-Original-Commit-Position: refs/heads/master@{#19676}
Cr-Mirrored-From: https://chromium.googlesource.com/external/webrtc
Cr-Mirrored-Commit: 77b7a1da2d90c46afba21ba38fac63b991b6e88d
diff --git a/modules/desktop_capture/BUILD.gn b/modules/desktop_capture/BUILD.gn
index 6b7fb22..2e6b168 100644
--- a/modules/desktop_capture/BUILD.gn
+++ b/modules/desktop_capture/BUILD.gn
@@ -58,6 +58,7 @@
       sources += [
         "screen_capturer_integration_test.cc",
         "screen_drawer_unittest.cc",
+        "window_finder_unittest.cc",
       ]
     }
   }
diff --git a/modules/desktop_capture/screen_drawer.h b/modules/desktop_capture/screen_drawer.h
index cde9a2e..ee0ebf0 100644
--- a/modules/desktop_capture/screen_drawer.h
+++ b/modules/desktop_capture/screen_drawer.h
@@ -16,6 +16,7 @@
 #include <memory>
 
 #include "webrtc/modules/desktop_capture/rgba_color.h"
+#include "webrtc/modules/desktop_capture/desktop_capture_types.h"
 #include "webrtc/modules/desktop_capture/desktop_geometry.h"
 
 namespace webrtc {
@@ -49,7 +50,8 @@
   // Returns the region inside which DrawRectangle() function are expected to
   // work, in capturer coordinates (assuming ScreenCapturer::SelectScreen has
   // not been called). This region may exclude regions of the screen reserved by
-  // the OS for things like menu bars or app launchers.
+  // the OS for things like menu bars or app launchers. The DesktopRect is in
+  // system coordinate, i.e. the primary monitor always starts from (0, 0).
   virtual DesktopRect DrawableRegion() = 0;
 
   // Draws a rectangle to cover |rect| with |color|. Note, rect.bottom() and
@@ -70,6 +72,10 @@
   // shapes will eventually be drawn on the screen, due to some OS limitations,
   // these shapes may be partially appeared sometimes.
   virtual bool MayDrawIncompleteShapes() = 0;
+
+  // Returns the id of the drawer window. This function returns kNullWindowId if
+  // the implementation does not draw on a window of the system.
+  virtual WindowId window_id() const = 0;
 };
 
 }  // namespace webrtc
diff --git a/modules/desktop_capture/screen_drawer_linux.cc b/modules/desktop_capture/screen_drawer_linux.cc
index fa2cfa4..1a99e69 100644
--- a/modules/desktop_capture/screen_drawer_linux.cc
+++ b/modules/desktop_capture/screen_drawer_linux.cc
@@ -36,6 +36,7 @@
   void Clear() override;
   void WaitForPendingDraws() override;
   bool MayDrawIncompleteShapes() override;
+  WindowId window_id() const override;
 
  private:
   // Bring the window to the front, this can help to avoid the impact from other
@@ -136,6 +137,10 @@
   return true;
 }
 
+WindowId ScreenDrawerLinux::window_id() const {
+  return window_;
+}
+
 void ScreenDrawerLinux::BringToFront() {
   Atom state_above = XInternAtom(display_->display(), "_NET_WM_STATE_ABOVE", 1);
   Atom window_state = XInternAtom(display_->display(), "_NET_WM_STATE", 1);
diff --git a/modules/desktop_capture/screen_drawer_win.cc b/modules/desktop_capture/screen_drawer_win.cc
index 7b1a56f..caa0ce2 100644
--- a/modules/desktop_capture/screen_drawer_win.cc
+++ b/modules/desktop_capture/screen_drawer_win.cc
@@ -82,6 +82,7 @@
   void Clear() override;
   void WaitForPendingDraws() override;
   bool MayDrawIncompleteShapes() override;
+  WindowId window_id() const override;
 
  private:
   // Bring the window to the front, this can help to avoid the impact from other
@@ -158,6 +159,10 @@
   return true;
 }
 
+WindowId ScreenDrawerWin::window_id() const {
+  return reinterpret_cast<WindowId>(window_);
+}
+
 void ScreenDrawerWin::DrawLine(DesktopVector start,
                                DesktopVector end,
                                RgbaColor color) {
diff --git a/modules/desktop_capture/window_finder.h b/modules/desktop_capture/window_finder.h
index 90f3513..f1ede97 100644
--- a/modules/desktop_capture/window_finder.h
+++ b/modules/desktop_capture/window_finder.h
@@ -11,11 +11,17 @@
 #ifndef WEBRTC_MODULES_DESKTOP_CAPTURE_WINDOW_FINDER_H_
 #define WEBRTC_MODULES_DESKTOP_CAPTURE_WINDOW_FINDER_H_
 
+#include <memory>
+
 #include "webrtc/modules/desktop_capture/desktop_capture_types.h"
 #include "webrtc/modules/desktop_capture/desktop_geometry.h"
 
 namespace webrtc {
 
+#if defined(USE_X11)
+class XAtomCache;
+#endif
+
 // An interface to return the id of the visible window under a certain point.
 class WindowFinder {
  public:
@@ -28,6 +34,17 @@
   // |point| is always in system coordinate, i.e. the primary monitor always
   // starts from (0, 0).
   virtual WindowId GetWindowUnderPoint(DesktopVector point) = 0;
+
+  struct Options {
+#if defined(USE_X11)
+    XAtomCache* cache = nullptr;
+#endif
+  };
+
+  // Creates a platform-independent WindowFinder implementation. This function
+  // returns nullptr if |options| does not contain enough information or
+  // WindowFinder does not support current platform.
+  static std::unique_ptr<WindowFinder> Create(const Options& options);
 };
 
 }  // namespace webrtc
diff --git a/modules/desktop_capture/window_finder_mac.mm b/modules/desktop_capture/window_finder_mac.mm
index ae4a911..8bf7150 100644
--- a/modules/desktop_capture/window_finder_mac.mm
+++ b/modules/desktop_capture/window_finder_mac.mm
@@ -13,6 +13,7 @@
 #include <CoreFoundation/CoreFoundation.h>
 
 #include "webrtc/modules/desktop_capture/mac/window_list_utils.h"
+#include "webrtc/rtc_base/ptr_util.h"
 
 namespace webrtc {
 
@@ -33,4 +34,10 @@
   return id;
 }
 
+// static
+std::unique_ptr<WindowFinder> WindowFinder::Create(
+    const WindowFinder::Options& options) {
+  return rtc::MakeUnique<WindowFinderMac>();
+}
+
 }  // namespace webrtc
diff --git a/modules/desktop_capture/window_finder_unittest.cc b/modules/desktop_capture/window_finder_unittest.cc
new file mode 100644
index 0000000..6121ded
--- /dev/null
+++ b/modules/desktop_capture/window_finder_unittest.cc
@@ -0,0 +1,161 @@
+/*
+ *  Copyright (c) 2017 The WebRTC project authors. All Rights Reserved.
+ *
+ *  Use of this source code is governed by a BSD-style license
+ *  that can be found in the LICENSE file in the root of the source
+ *  tree. An additional intellectual property rights grant can be found
+ *  in the file PATENTS.  All contributing project authors may
+ *  be found in the AUTHORS file in the root of the source tree.
+ */
+
+#include "webrtc/modules/desktop_capture/window_finder.h"
+
+#include <stdint.h>
+
+#include <memory>
+
+#include "webrtc/modules/desktop_capture/desktop_geometry.h"
+#include "webrtc/modules/desktop_capture/screen_drawer.h"
+#include "webrtc/rtc_base/logging.h"
+#include "webrtc/test/gtest.h"
+
+#if defined(USE_X11)
+#include "webrtc/modules/desktop_capture/x11/shared_x_display.h"
+#include "webrtc/modules/desktop_capture/x11/x_atom_cache.h"
+#include "webrtc/rtc_base/ptr_util.h"
+#endif
+
+#if defined(WEBRTC_WIN)
+#include <windows.h>
+
+#include "webrtc/modules/desktop_capture/window_finder_win.h"
+#include "webrtc/modules/desktop_capture/win/window_capture_utils.h"
+#endif
+
+namespace webrtc {
+
+namespace {
+
+#if defined(WEBRTC_WIN)
+// ScreenDrawerWin does not have a message loop, so it's unresponsive to user
+// inputs. WindowFinderWin cannot detect this kind of unresponsive windows.
+// Instead, console window is used to test WindowFinderWin.
+TEST(WindowFinderTest, FindConsoleWindow) {
+  // Creates a ScreenDrawer to avoid this test from conflicting with
+  // ScreenCapturerIntegrationTest: both tests require its window to be in
+  // foreground.
+  //
+  // In ScreenCapturer related tests, this is controlled by
+  // ScreenDrawer, which has a global lock to ensure only one ScreenDrawer
+  // window is active. So even we do not use ScreenDrawer for Windows test,
+  // creating an instance can block ScreenCapturer related tests until this test
+  // finishes.
+  //
+  // Usually the test framework should take care of this "isolated test"
+  // requirement, but unfortunately WebRTC trybots do not support this.
+  std::unique_ptr<ScreenDrawer> drawer = ScreenDrawer::Create();
+  const int kMaxSize = 10000;
+  // Enlarges current console window.
+  system("mode 1000,1000");
+  const HWND console_window = GetConsoleWindow();
+  MoveWindow(console_window, 0, 0, kMaxSize, kMaxSize, true);
+
+  // Brings console window to top.
+  SetWindowPos(
+      console_window, HWND_TOPMOST, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE);
+  BringWindowToTop(console_window);
+
+  WindowFinderWin finder;
+  for (int i = 0; i < kMaxSize; i++) {
+    const DesktopVector spot(i, i);
+    const HWND id = reinterpret_cast<HWND>(finder.GetWindowUnderPoint(spot));
+    if (id == console_window) {
+      return;
+    }
+  }
+
+  FAIL();
+}
+
+#else
+TEST(WindowFinderTest, FindDrawerWindow) {
+  WindowFinder::Options options;
+#if defined(USE_X11)
+  std::unique_ptr<XAtomCache> cache;
+  const auto shared_x_display = SharedXDisplay::CreateDefault();
+  if (shared_x_display) {
+    cache = rtc::MakeUnique<XAtomCache>(shared_x_display->display());
+    options.cache = cache.get();
+  }
+#endif
+  std::unique_ptr<WindowFinder> finder = WindowFinder::Create(options);
+  if (!finder) {
+    LOG(LS_WARNING) << "No WindowFinder implementation for current platform.";
+    return;
+  }
+
+  std::unique_ptr<ScreenDrawer> drawer = ScreenDrawer::Create();
+  if (!drawer) {
+    LOG(LS_WARNING) << "No ScreenDrawer implementation for current platform.";
+    return;
+  }
+
+  if (drawer->window_id() == kNullWindowId) {
+    // TODO(zijiehe): WindowFinderTest can use a dedicated window without
+    // relying on ScreenDrawer.
+    LOG(LS_WARNING) << "ScreenDrawer implementation for current platform does "
+                       "create a window.";
+    return;
+  }
+
+  // ScreenDrawer may not be able to bring the window to the top. So we test
+  // several spots, at least one of them should succeed.
+  const DesktopRect region = drawer->DrawableRegion();
+  if (region.is_empty()) {
+    LOG(LS_WARNING) << "ScreenDrawer::DrawableRegion() is too small for the "
+                       "WindowFinderTest.";
+    return;
+  }
+
+  for (int i = 0; i < region.width(); i++) {
+    const DesktopVector spot(
+        region.left() + i, region.top() + i * region.height() / region.width());
+    const WindowId id = finder->GetWindowUnderPoint(spot);
+    if (id == drawer->window_id()) {
+      return;
+    }
+  }
+
+  FAIL();
+}
+#endif
+
+TEST(WindowFinderTest, ShouldReturnNullWindowIfSpotIsOutOfScreen) {
+  WindowFinder::Options options;
+#if defined(USE_X11)
+  std::unique_ptr<XAtomCache> cache;
+  const auto shared_x_display = SharedXDisplay::CreateDefault();
+  if (shared_x_display) {
+    cache = rtc::MakeUnique<XAtomCache>(shared_x_display->display());
+    options.cache = cache.get();
+  }
+#endif
+  std::unique_ptr<WindowFinder> finder = WindowFinder::Create(options);
+  if (!finder) {
+    LOG(LS_WARNING) << "No WindowFinder implementation for current platform.";
+    return;
+  }
+
+  ASSERT_EQ(kNullWindowId, finder->GetWindowUnderPoint(
+      DesktopVector(INT16_MAX, INT16_MAX)));
+  ASSERT_EQ(kNullWindowId, finder->GetWindowUnderPoint(
+      DesktopVector(INT16_MAX, INT16_MIN)));
+  ASSERT_EQ(kNullWindowId, finder->GetWindowUnderPoint(
+      DesktopVector(INT16_MIN, INT16_MAX)));
+  ASSERT_EQ(kNullWindowId, finder->GetWindowUnderPoint(
+      DesktopVector(INT16_MIN, INT16_MIN)));
+}
+
+}  // namespace
+
+}  // namespace webrtc
diff --git a/modules/desktop_capture/window_finder_win.cc b/modules/desktop_capture/window_finder_win.cc
index 8d742cd..19dbea3 100644
--- a/modules/desktop_capture/window_finder_win.cc
+++ b/modules/desktop_capture/window_finder_win.cc
@@ -12,6 +12,8 @@
 
 #include <windows.h>
 
+#include "webrtc/rtc_base/ptr_util.h"
+
 namespace webrtc {
 
 WindowFinderWin::WindowFinderWin() = default;
@@ -35,4 +37,10 @@
   return reinterpret_cast<WindowId>(window);
 }
 
+// static
+std::unique_ptr<WindowFinder> WindowFinder::Create(
+    const WindowFinder::Options& options) {
+  return rtc::MakeUnique<WindowFinderWin>();
+}
+
 }  // namespace webrtc
diff --git a/modules/desktop_capture/window_finder_x11.cc b/modules/desktop_capture/window_finder_x11.cc
index 4c6da9d..d929460 100644
--- a/modules/desktop_capture/window_finder_x11.cc
+++ b/modules/desktop_capture/window_finder_x11.cc
@@ -12,6 +12,7 @@
 
 #include "webrtc/modules/desktop_capture/x11/window_list_utils.h"
 #include "webrtc/rtc_base/checks.h"
+#include "webrtc/rtc_base/ptr_util.h"
 
 namespace webrtc {
 
@@ -37,4 +38,14 @@
   return id;
 }
 
+// static
+std::unique_ptr<WindowFinder> WindowFinder::Create(
+    const WindowFinder::Options& options) {
+  if (options.cache == nullptr) {
+    return nullptr;
+  }
+
+  return rtc::MakeUnique<WindowFinderX11>(options.cache);
+}
+
 }  // namespace webrtc