chromium/ui/views/widget/any_widget_observer.h

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

#ifndef UI_VIEWS_WIDGET_ANY_WIDGET_OBSERVER_H_
#define UI_VIEWS_WIDGET_ANY_WIDGET_OBSERVER_H_

#include <string>

#include "base/functional/callback.h"
#include "base/memory/weak_ptr.h"
#include "base/observer_list_types.h"
#include "base/run_loop.h"
#include "ui/views/views_export.h"

namespace views {

namespace internal {
class AnyWidgetObserverSingleton;
}

namespace test {
class AnyWidgetTestPasskey;
}

class AnyWidgetPasskey;
class Widget;

// AnyWidgetObserver is used when you want to observe widget changes but don't
// have an easy way to get a reference to the Widget in question, usually
// because it is created behind a layer of abstraction that hides the Widget.
//
// This class should only be used as a last resort - if you find yourself
// reaching for it in production code, it probably means some parts of your
// system aren't coupled enough or your API boundaries are hiding too much. You
// will need review from an owner of this class to add new uses of it, because
// it requires a Passkey to construct it - see //docs/patterns/passkey.md for
// details. Uses in tests can be done freely using
// views::test::AnyWidgetTestPasskey.
//
// This class can be used for waiting for a particular View being shown, as in:
//
//    RunLoop run_loop;
//    AnyWidgetObserver observer(views::test::AnyWidgetTestPasskey{});
//    Widget* widget;
//    observer.set_initialized_callback(
//        base::BindLambdaForTesting([&](Widget* w) {
//            if (w->GetName() == "MyWidget") {
//                widget = w;
//                run_loop.Quit();
//            }
//        }));
//    ThingThatCreatesWidget();
//    run_loop.Run();
//
// with your widget getting the name "MyWidget" via InitParams::name.
//
// Note: if you're trying to wait for a named widget to be *shown*, there is an
// ergonomic wrapper for that - see NamedWidgetShownWaiter below. You use it
// like this:
//
//    NamedWidgetShownWaiter waiter(
//        views::test::AnyWidgetTestPasskey{}, "MyWidget");
//    ThingThatCreatesAndShowsWidget();
//    Widget* widget = waiter.WaitIfNeededAndGet();
//
// This class can also be used to make sure a named widget is _not_ shown, as
// this particular example (intended for testing code) shows:
//
// AnyWidgetObserver observer(views::test::AnyWidgetTestPasskey{});
// observer.set_shown_callback(
//    base::BindLambdaForTesting([&](views::Widget* widget) {
//        ASSERT_FALSE(widget->GetName() == "MyWidget");
//      }));
//
// TODO(ellyjones): Add Widget::SetDebugName and add a remark about that here.
//
// This allows you to create a test that is robust even if
// ThingThatCreatesAndShowsWidget() later becomes asynchronous, takes a few
// milliseconds, etc.
class VIEWS_EXPORT AnyWidgetObserver : public base::CheckedObserver {};

// NamedWidgetShownWaiter provides a more ergonomic way to do the most common
// thing clients want to use AnyWidgetObserver for: waiting for a Widget with a
// specific name to be shown. Use it like:
//
//    NamedWidgetShownWaiter waiter(Passkey{}, "MyDialogView");
//    ThingThatShowsDialog();
//    Widget* dialog_widget = waiter.WaitIfNeededAndGet();
//
// It is important that NamedWidgetShownWaiter be constructed before any code
// that might show the named Widget, because if the Widget is shown before the
// waiter is constructed, the waiter won't have observed the show and will
// instead hang forever. Worse, if the widget *sometimes* shows before the
// waiter is constructed and sometimes doesn't, you will be introducing flake.
// THIS IS A DANGEROUS PATTERN, DON'T DO THIS:
//
//   ThingThatShowsDialogAsynchronously();
//   NamedWidgetShownWaiter waiter(...);
//   waiter.WaitIfNeededAndGet();
class VIEWS_EXPORT NamedWidgetShownWaiter {};

class AnyWidgetPasskey {};

namespace test {

// A passkey class to allow tests to use AnyWidgetObserver without needing a
// views owner signoff.
class AnyWidgetTestPasskey {};

}  // namespace test

}  // namespace views

#endif  // UI_VIEWS_WIDGET_ANY_WIDGET_OBSERVER_H_