1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218
|
// 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;
}
// 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);
AnyWidgetCallback initialized_callback_;
AnyWidgetCallback shown_callback_;
AnyWidgetCallback hidden_callback_;
AnyWidgetCallback closing_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_
|