blob: 1a780159367835bfd842a492aceeb5976840ddeb [file] [log] [blame]
// 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 breadcrumbs {
class ApplicationBreadcrumbsLogger;
}
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 {
public:
using AnyWidgetCallback = base::RepeatingCallback<void(Widget*)>;
// Using this in production requires an AnyWidgetPasskey, which can only be
// constructed by a list of allowed friend classes...
explicit AnyWidgetObserver(AnyWidgetPasskey passkey);
// ... but for tests or debugging, anyone can construct a
// views::test::AnyWidgetTestPasskey.
explicit AnyWidgetObserver(test::AnyWidgetTestPasskey passkey);
~AnyWidgetObserver() override;
AnyWidgetObserver(const AnyWidgetObserver& other) = delete;
AnyWidgetObserver& operator=(const AnyWidgetObserver& other) = delete;
// This sets the callback for when the Widget has finished initialization and
// is ready to use. This is the first point at which the widget is useable.
void set_initialized_callback(const AnyWidgetCallback& callback) {
initialized_callback_ = callback;
}
// These set the callbacks for when the backing native widget has just been
// asked to change visibility. Note that the native widget may or may not
// actually be drawn on the screen when these callbacks are run, because there
// are more layers of asynchronousness at the OS layer.
void set_shown_callback(const AnyWidgetCallback& callback) {
// Check out NamedWidgetShownWaiter for an alternative to this method.
shown_callback_ = callback;
}
void set_hidden_callback(const AnyWidgetCallback& callback) {
hidden_callback_ = callback;
}
// This sets the callback for when the Widget has decided that it is about to
// close, but not yet begun to tear down the backing native Widget or the
// RootView. This is the last point at which the Widget is in a consistent,
// useable state.
void set_closing_callback(const AnyWidgetCallback& callback) {
closing_callback_ = callback;
}
// This sets the callback for when the widget becomes active.
void set_activated_callback(const AnyWidgetCallback& callback) {
activated_callback_ = callback;
}
// These two methods deliberately don't exist:
// void set_created_callback(...)
// void set_destroyed_callback(...)
// They would be called respectively too early and too late in the Widget's
// lifecycle for it to be usefully identified - at construction time the
// Widget has no properties or contents yet (and therefore can't be
// meaningfully identified as "your widget" for test purposes), and at
// destruction time the Widget's state is already partly destroyed by the
// closure process. Both methods are deliberately left out to avoid dangerous
// designs based on them.
private:
friend class internal::AnyWidgetObserverSingleton;
AnyWidgetObserver();
// Called from the singleton to propagate events to each AnyWidgetObserver.
void OnAnyWidgetInitialized(Widget* widget);
void OnAnyWidgetShown(Widget* widget);
void OnAnyWidgetHidden(Widget* widget);
void OnAnyWidgetClosing(Widget* widget);
void OnAnyWidgetActivated(Widget* widget);
AnyWidgetCallback initialized_callback_;
AnyWidgetCallback shown_callback_;
AnyWidgetCallback hidden_callback_;
AnyWidgetCallback closing_callback_;
AnyWidgetCallback activated_callback_;
};
// 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 {
public:
NamedWidgetShownWaiter(AnyWidgetPasskey passkey, const std::string& name);
NamedWidgetShownWaiter(test::AnyWidgetTestPasskey passkey,
const std::string& name);
virtual ~NamedWidgetShownWaiter();
Widget* WaitIfNeededAndGet();
private:
explicit NamedWidgetShownWaiter(const std::string& name);
void OnAnyWidgetShown(Widget* widget);
AnyWidgetObserver observer_;
base::WeakPtr<Widget> widget_;
base::RunLoop run_loop_;
const std::string name_;
};
class AnyWidgetPasskey {
private:
AnyWidgetPasskey() = default; // NOLINT
// Add friend classes here that are allowed to use AnyWidgetObserver in
// production code.
friend class NamedWidgetShownWaiter;
friend class breadcrumbs::ApplicationBreadcrumbsLogger;
};
namespace test {
// A passkey class to allow tests to use AnyWidgetObserver without needing a
// views owner signoff.
class AnyWidgetTestPasskey {
public:
AnyWidgetTestPasskey() = default;
};
} // namespace test
} // namespace views
#endif // UI_VIEWS_WIDGET_ANY_WIDGET_OBSERVER_H_