blob: d6af0f392a7e8d07e790aa79a8c2a50e1270f042 [file] [log] [blame]
// Copyright 2016 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 "chrome/browser/ui/test/test_browser_dialog.h"
#include "base/command_line.h"
#include "base/run_loop.h"
#include "base/stl_util.h"
#include "base/test/gtest_util.h"
#include "base/test/scoped_feature_list.h"
#include "base/threading/thread_task_runner_handle.h"
#include "build/build_config.h"
#include "chrome/browser/platform_util.h"
#include "chrome/common/chrome_features.h"
#include "ui/base/ui_base_features.h"
#include "ui/views/test/widget_test.h"
#include "ui/views/widget/widget.h"
#include "ui/views/widget/widget_observer.h"
#if defined(OS_CHROMEOS)
#include "ash/public/cpp/config.h"
#include "ash/shell.h" // mash-ok
#include "chrome/browser/chromeos/ash_config.h"
#endif
#if defined(OS_MACOSX)
#include "chrome/browser/ui/test/test_browser_dialog_mac.h"
#endif
namespace {
// An automatic action for WidgetCloser to post to the RunLoop.
// TODO(tapted): Explore asynchronous Widget::Close() and DialogClientView::
// {Accept,Cancel}Window() approaches to test other dialog lifetimes.
enum class DialogAction {
INTERACTIVE, // Run interactively.
CLOSE_NOW, // Call Widget::CloseNow().
CLOSE, // Call Widget::Close().
};
// Helper to break out of the nested run loop that runs a test dialog.
class WidgetCloser : public views::WidgetObserver {
public:
WidgetCloser(views::Widget* widget, DialogAction action)
: action_(action), widget_(widget), weak_ptr_factory_(this) {
widget->AddObserver(this);
if (action == DialogAction::INTERACTIVE)
return;
base::ThreadTaskRunnerHandle::Get()->PostTask(
FROM_HERE, base::BindOnce(&WidgetCloser::CloseAction,
weak_ptr_factory_.GetWeakPtr()));
}
// WidgetObserver:
void OnWidgetDestroyed(views::Widget* widget) override {
widget_->RemoveObserver(this);
widget_ = nullptr;
run_loop_.Quit();
}
void Wait() { run_loop_.Run(); }
private:
void CloseAction() {
if (!widget_)
return;
switch (action_) {
case DialogAction::CLOSE_NOW:
widget_->CloseNow();
break;
case DialogAction::CLOSE:
widget_->Close();
break;
case DialogAction::INTERACTIVE:
NOTREACHED();
break;
}
}
base::RunLoop run_loop_;
const DialogAction action_;
views::Widget* widget_;
base::WeakPtrFactory<WidgetCloser> weak_ptr_factory_;
DISALLOW_COPY_AND_ASSIGN(WidgetCloser);
};
// Extracts the |name| argument for ShowDialog() from the current test case.
// E.g. for InvokeDialog_name (or DISABLED_InvokeDialog_name) returns "name".
std::string NameFromTestCase() {
const std::string name = base::TestNameWithoutDisabledPrefix(
testing::UnitTest::GetInstance()->current_test_info()->name());
std::string::size_type underscore = name.find('_');
return underscore == std::string::npos ? std::string()
: name.substr(underscore + 1);
}
} // namespace
TestBrowserDialog::TestBrowserDialog() {}
void TestBrowserDialog::RunDialog() {
#if defined(OS_MACOSX)
// The rest of this method assumes the child dialog is toolkit-views. So, for
// Mac, it will only work when MD for secondary UI is enabled. Without this, a
// Cocoa dialog will be created, which TestBrowserDialog doesn't support.
// Force kSecondaryUiMd on Mac to get coverage on the bots. Leave it optional
// elsewhere so that the non-MD dialog can be invoked to compare. Note that
// since SetUp() has already been called, some parts of the toolkit may
// already be initialized without MD - this is just to ensure Cocoa dialogs
// are not selected.
base::test::ScopedFeatureList enable_views_on_mac_always;
enable_views_on_mac_always.InitWithFeatures(
{features::kSecondaryUiMd, features::kShowAllDialogsWithViewsToolkit},
{});
#endif
views::Widget::Widgets widgets_before =
views::test::WidgetTest::GetAllWidgets();
#if defined(OS_CHROMEOS)
// GetAllWidgets() uses AuraTestHelper to find the aura root window, but
// that's not used on browser_tests, so ask ash. Under mash the MusClient
// provides the list of root windows, so this isn't needed.
if (chromeos::GetAshConfig() != ash::Config::MASH) {
views::Widget::GetAllChildWidgets(ash::Shell::GetPrimaryRootWindow(),
&widgets_before);
}
#endif // OS_CHROMEOS
ShowDialog(NameFromTestCase());
views::Widget::Widgets widgets_after =
views::test::WidgetTest::GetAllWidgets();
#if defined(OS_CHROMEOS)
if (chromeos::GetAshConfig() != ash::Config::MASH) {
views::Widget::GetAllChildWidgets(ash::Shell::GetPrimaryRootWindow(),
&widgets_after);
}
#endif // OS_CHROMEOS
auto added = base::STLSetDifference<std::vector<views::Widget*>>(
widgets_after, widgets_before);
if (added.size() > 1) {
// Some tests create a standalone window to anchor a dialog. In those cases,
// ignore added Widgets that are not dialogs.
base::EraseIf(added, [](views::Widget* widget) {
return !widget->widget_delegate()->AsDialogDelegate();
});
}
// This can fail if no dialog was shown, if the dialog shown wasn't a toolkit-
// views dialog, or if more than one child dialog was shown.
ASSERT_EQ(1u, added.size());
DialogAction action = DialogAction::CLOSE_NOW;
if (base::CommandLine::ForCurrentProcess()->HasSwitch(
internal::kInteractiveSwitch)) {
action = DialogAction::INTERACTIVE;
} else if (AlwaysCloseAsynchronously()) {
// TODO(tapted): Iterate over close methods when non-interactive for greater
// test coverage.
action = DialogAction::CLOSE;
}
WidgetCloser closer(added[0], action);
#if defined(OS_MACOSX)
internal::TestBrowserDialogInteractiveSetUp();
#endif
closer.Wait();
}
void TestBrowserDialog::UseMdOnly() {
#if defined(OS_MACOSX)
maybe_enable_md_.InitWithFeatures(
{features::kSecondaryUiMd, features::kShowAllDialogsWithViewsToolkit},
{});
#else
maybe_enable_md_.InitWithFeatures({features::kSecondaryUiMd}, {});
#endif
}
bool TestBrowserDialog::AlwaysCloseAsynchronously() {
return false;
}