chromium/chrome/test/base/interactive_test_utils_win.cc

// Copyright 2012 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#ifdef UNSAFE_BUFFERS_BUILD
// TODO(crbug.com/40285824): Remove this and convert code to safer constructs.
#pragma allow_unsafe_buffers
#endif

#include "chrome/test/base/interactive_test_utils.h"

#include <windows.h>

#include <Psapi.h>

#include <memory>

#include "base/command_line.h"
#include "base/files/file_path.h"
#include "base/functional/bind.h"
#include "base/location.h"
#include "base/logging.h"
#include "base/process/process_handle.h"
#include "base/test/test_timeouts.h"
#include "base/threading/platform_thread.h"
#include "base/time/time.h"
#include "base/timer/timer.h"
#include "build/chromeos_buildflags.h"
#include "chrome/test/base/interactive_test_utils_aura.h"
#include "chrome/test/base/process_lineage_win.h"
#include "chrome/test/base/save_desktop_snapshot.h"
#include "chrome/test/base/window_contents_as_string_win.h"
#include "ui/aura/window_tree_host.h"
#include "ui/base/test/ui_controls.h"
#include "ui/base/win/foreground_helper.h"
#include "ui/views/focus/focus_manager.h"

namespace ui_test_utils {

void HideNativeWindow(gfx::NativeWindow window) {
#if BUILDFLAG(IS_CHROMEOS_ASH)
  HideNativeWindowAura(window);
#else
  HWND hwnd = window->GetHost()->GetAcceleratedWidget();
  ::ShowWindow(hwnd, SW_HIDE);
#endif  // BUILDFLAG(IS_CHROMEOS_ASH)
}

bool ShowAndFocusNativeWindow(gfx::NativeWindow window) {
#if BUILDFLAG(IS_CHROMEOS_ASH)
  ShowAndFocusNativeWindowAura(window);
#endif  // BUILDFLAG(IS_CHROMEOS_ASH)
  window->Show();
  // Always make sure the window hosting ash is visible and focused.
  HWND hwnd = window->GetHost()->GetAcceleratedWidget();

  ::ShowWindow(hwnd, SW_SHOW);

  int attempts_left = 5;
  bool have_snapshot = false;
  while (true) {
    if (::GetForegroundWindow() == hwnd)
      return true;

    VLOG(1) << "Forcefully refocusing front window";
    ui::ForegroundHelper::SetForeground(hwnd);

    // ShowWindow does not necessarily activate the window. In particular if a
    // window from another app is the foreground window then the request to
    // activate the window fails. See SetForegroundWindow for details.
    HWND foreground_window = ::GetForegroundWindow();
    if (foreground_window == hwnd)
      return true;

    // Emit some diagnostic information about the foreground window and its
    // owning process.
    wchar_t window_title[256];
    GetWindowText(foreground_window, window_title, std::size(window_title));

    std::wstring lineage_str;
    std::wstring window_contents;
    DWORD foreground_process_id = 0;
    if (foreground_window) {
      GetWindowThreadProcessId(foreground_window, &foreground_process_id);
      ProcessLineage lineage = ProcessLineage::Create(foreground_process_id);
      if (!lineage.IsEmpty()) {
        lineage_str = L", process lineage: ";
        lineage_str.append(lineage.ToString());
      }

      window_contents = WindowContentsAsString(foreground_window);
    }
    LOG(ERROR) << "ShowAndFocusNativeWindow found a foreground window: "
               << foreground_window << ", title: " << window_title
               << lineage_str << ", contents:" << std::endl
               << window_contents;

    // Take a snapshot of the screen.
    const base::FilePath output_dir =
        base::CommandLine::ForCurrentProcess()->GetSwitchValuePath(
            kSnapshotOutputDir);
    if (!have_snapshot && !output_dir.empty()) {
      base::FilePath snapshot_file = SaveDesktopSnapshot(output_dir);
      if (!snapshot_file.empty()) {
        have_snapshot = true;
        LOG(ERROR) << "Screenshot saved to file: \"" << snapshot_file.value()
                   << "\"";
      }
    }

    if (!attempts_left--) {
      LOG(ERROR) << "ShowAndFocusNativeWindow failed after too many attempts.";
      break;
    }

    // Give up if there is no foreground window or if it's mysteriously from
    // this process.
    if (!foreground_window) {
      LOG(ERROR) << "ShowAndFocusNativeWindow failed to focus any window.";
      break;
    }

    if (foreground_process_id == base::GetCurrentProcId()) {
      LOG(ERROR) << "ShowAndFocusNativeWindow failed because another window in"
                    " the test process will not give up focus.";
      break;
    }

    // Attempt to close the offending window.
    ::PostMessageW(foreground_window, WM_CLOSE, 0, 0);
    // Poll to wait for the window to be destroyed. While it is possible to
    // avoid polling via use of UI Automation to observe the closing of the
    // window, the code to do so is non-trivial and requires use of another
    // thread in the MTA.
    base::RunLoop run_loop;
    base::RepeatingTimer timer(
        FROM_HERE, TestTimeouts::tiny_timeout(),
        base::BindRepeating(
            [](HWND foreground_window,
               const base::RepeatingClosure& quit_closure, int* polls) {
              if (!*polls-- || ::GetForegroundWindow() != foreground_window)
                quit_closure.Run();
            },
            foreground_window, run_loop.QuitClosure(),
            base::Owned(std::make_unique<int>(
                TestTimeouts::action_timeout().InMicroseconds() /
                TestTimeouts::tiny_timeout().InMicroseconds()))));
    timer.Reset();
    run_loop.Run();
    if (::GetForegroundWindow() == foreground_window) {
      LOG(ERROR) << "ShowAndFocusNativeWindow timed out closing the "
                    "foreground window.";
      break;
    }
    // Otherwise, loop around and try focusing the desired window again.
  }

  return false;
}

}  // namespace ui_test_utils