In the common case, a View
instance lives within a hierarchy of View
s, up to a RootView
object that is owned by a Widget
. Calling AddChildView<>()
typically passes ownership of the child to the parent, while RemoveChildView[T<>]()
gives ownership back to the caller. While other ownership patterns exist, newly-added code should use this one.
Accordingly, the best practices for ownership and lifetime management in Views code are similar to those in the rest of Chromium, but include some details specific to the Views APIs.
std::make_unique<>()
when creating objects, and distinguish owning and non-owning uses by using smart and raw pointer types.View
. It also DCHECK()
s that set_owned_by_client()
has not been called, preventing double-ownership.Avoid
Typical code from dice_bubble_sync_promo_view.cc
using bare new
:
Best practice
Rewriting using the unique_ptr<>
version of AddChildView<>()
is shorter and safer:
... views::Label* title = new views::Label( title_text, views::style::CONTEXT_DIALOG_BODY_TEXT, text_style); title->SetHorizontalAlignment( gfx::HorizontalAlignment::ALIGN_LEFT); title->SetMultiLine(true); AddChildView(title); ... signin_button_view_ = new DiceSigninButtonView( account, account_icon, this, /*use_account_name_as_title=*/true); AddChildView(signin_button_view_); ...
... auto* title = AddChildView( std::make_unique<views::Label>( title_text, views::style::CONTEXT_DIALOG_BODY_TEXT, text_style)); title->SetHorizontalAlignment( gfx::HorizontalAlignment::ALIGN_LEFT); title->SetMultiLine(true); ... signin_button_view_ = AddChildView( std::make_unique<DiceSigninButtonView>( account, account_icon, this, /*use_account_name_as_title=*/true)); ...
The View::set_owned_by_client()
flag means that a View
is owned by something other than its parent View
. This method is deprecated (and will eventually be removed) since it results in APIs that are easy to misuse, code where ownership is unclear, and a higher likelihood of bugs. Needing this flag may be a signal that the View
subclass in question is too heavyweight, and refactoring using MVC or a similar paradigm would allow a long-lived “model” or “controller” with more-transient “view”s.
Avoid
Code in time_view.{h,cc}
that uses set_owned_by_client()
to have non-parented View
s, so it can swap layouts without recreating the children:
Best practice
Rewriting using subclasses to encapsulate layout allows the parent to merely adjust visibility:
class ASH_EXPORT TimeView : public ActionableView, public ClockObserver { ... private: std::unique_ptr<views::Label> horizontal_label_; ... std::unique_ptr<views::Label> vertical_label_hours_; std::unique_ptr<views::Label> vertical_label_minutes_; ... }; void TimeView::SetupLabels() { horizontal_label_.reset(new views::Label()); SetupLabel(horizontal_label_.get()); vertical_label_hours_.reset(new views::Label()); SetupLabel(vertical_label_hours_.get()); vertical_label_minutes_.reset(new views::Label()); SetupLabel(vertical_label_minutes_.get()); ... } void TimeView::SetupLabel(views::Label* label) { label->set_owned_by_client(); ... } void TimeView::UpdateClockLayout( ClockLayout clock_layout) { ... if (clock_layout == ClockLayout::HORIZONTAL_CLOCK) { RemoveChildView(vertical_label_hours_.get()); RemoveChildView(vertical_label_minutes_.get()); SetLayoutManager( std::make_unique<views::FillLayout>()); AddChildView(horizontal_label_.get()); } else { RemoveChildView(horizontal_label_.get()); // Remove the current layout manager since it could // be the FillLayout which only allows one child. SetLayoutManager(nullptr); // Pre-add the children since ownership is being // retained by this. AddChildView(vertical_label_hours_.get()); AddChildView(vertical_label_minutes_.get()); views::GridLayout* layout = SetLayoutManager( std::make_unique<views::GridLayout>()); ... } Layout(); }
class ASH_EXPORT TimeView : public ActionableView, public ClockObserver { ... private: class HorizontalLabelView; class VerticalLabelView; ... HorizontalLabelView* horizontal_label_; VerticalLabelView* vertical_label_; ... }; TimeView::HorizontalLabelView::HorizontalLabelView() { SetLayoutManager( std::make_unique<views::FillLayout>()); ... } TimeView::VerticalLabelView::VerticalLabelView() { views::GridLayout* layout = SetLayoutManager( std::make_unique<views::GridLayout>()); ... } void TimeView::TimeView(ClockLayout clock_layout, ClockModel* model) { ... horizontal_label_ = AddChildView( std::make_unique<HorizontalLabelView>()); vertical_label_ = AddChildView( std::make_unique<VerticalLabelView>()); ... } void TimeView::UpdateClockLayout( ClockLayout clock_layout) { ... const bool is_horizontal = clock_layout == ClockLayout::HORIZONTAL_CLOCK; horizontal_label_->SetVisible(is_horizontal); vertical_label_->SetVisible(!is_horizontal); Layout(); }
Refcounting and WeakPtr
s may also be indicators that a View
is doing more than merely displaying UI. Views objects should only handle UI. Refcounting and WeakPtr
needs should generally be handled by helper objects.
Avoid
Old code in cast_dialog_no_sinks_view.{h,cc}
that used weak pointers to PostDelayedTask()
to itself:
Best practice
Current version eliminates lifetime concerns by using a OneShotTimer
, which is canceled when destroyed:
class CastDialogNoSinksView ... { ... private: base::WeakPtrFactory<CastDialogNoSinksView> weak_factory_{this}; ... }; CastDialogNoSinksView::CastDialogNoSinksView( Profile* profile) : profile_(profile) { ... content::GetUIThreadTaskRunner({})->PostDelayedTask( FROM_HERE, base::BindOnce( &CastDialogNoSinksView::ShowHelpIconView, weak_factory_.GetWeakPtr()), kSearchWaitTime); }
class CastDialogNoSinksView ... { ... private: base::OneShotTimer timer_; ... }; CastDialogNoSinksView::CastDialogNoSinksView( Profile* profile) : profile_(profile) { ... timer_.Start( FROM_HERE, kSearchWaitTime, base::BindOnce( &CastDialogNoSinksView::SetHelpIconView, base::Unretained(this))); }
View::RemoveChildViewT<>()
clearly conveys ownership transfer from the parent View
to the caller. Callers who wish to delete a View
can simply ignore the return argument. This is preferable to calling RemoveChildView()
and deleting the raw pointer (cumbersome and error-prone), calling RemoveChildView()
without ever deleting the pointer (leaks the View
), or simply deleting a pointer to a still-parented View
(will work today, but is semantically incorrect and may be removed in the future).
Avoid
Typical code in network_list_view.cc
which manually deletes a child after removing it:
Best practice
Rewriting using RemoveChildViewT<>()
is shorter and safer:
... if (mobile_header_view_) { scroll_content()->RemoveChildView( mobile_header_view_); delete mobile_header_view_; mobile_header_view_ = nullptr; needs_relayout_ = true; } ...
... if (mobile_header_view_) { scroll_content()->RemoveChildViewT( mobile_header_view_); mobile_header_view_ = nullptr; needs_relayout_ = true; } ...
Prefer scoping objects to paired Add/Remove-type calls. For example, use a base::ScopedObservation<>
instead of directly calling View::AddObserver()
and RemoveObserver()
. Such objects reduce the likelihood of use-after-free.
Avoid
Typical code in avatar_toolbar_button.cc
that uses paired add/remove calls:
Best practice
Rewriting using base::ScopedObservation<>
eliminates the destructor body entirely:
AvatarToolbarButton::AvatarToolbarButton( Browser* browser, ToolbarIconContainerView* parent) : browser_(browser), parent_(parent) { ... if (parent_) parent_->AddObserver(this); } AvatarToolbarButton::~AvatarToolbarButton() { if (parent_) parent_->RemoveObserver(this); }
class AvatarToolbarButton : public ToolbarButton, public ToolbarIconContainerView::Observer { ... private: base::ScopedObservation<AvatarToolbarButton, ToolbarIconContainerView::Observer> observation_{this}; }; AvatarToolbarButton::AvatarToolbarButton( Browser* browser, ToolbarIconContainerView* parent) : browser_(browser), parent_(parent) { ... if (parent_) observation_.Observe(parent_); } AvatarToolbarButton::~AvatarToolbarButton() = default;
For objects you own, destroy in the reverse order you created, so lifetimes are nested rather than partially-overlapping. This can also reduce the likelihood of use-after-free, usually by enabling code to make simplifying assumptions (e.g. that an observed object always outlives this
).
Avoid
Old code in widget_interactive_uitest.cc
that destroys in the same order as creation:
Best practice
Current version uses scoping objects that are simpler and safer:
TEST_F(WidgetTestInteractive, ViewFocusOnWidgetActivationChanges) { Widget* widget1 = CreateTopLevelPlatformWidget(); ... Widget* widget2 = CreateTopLevelPlatformWidget(); ... widget1->CloseNow(); widget2->CloseNow(); }
TEST_F(WidgetTestInteractive, ViewFocusOnWidgetActivationChanges) { WidgetAutoclosePtr widget1( CreateTopLevelPlatformWidget()); ... WidgetAutoclosePtr widget2( CreateTopLevelPlatformWidget()); ... }
Always use View
s on the main (UI) thread. Like most Chromium code, the Views toolkit is not thread-safe.
In most cases, add child View
s in a View
's constructor. From an ownership perspective, doing so is safe even though the View
is not yet in a Widget
; if the View
is destroyed before ever being placed in a Widget
, it will still destroy its child View
s. Child View
s may need to be added at other times (e.g. in AddedToWidget()
or OnThemeChanged()
, if constructing the View
requires a color; or lazily, if creation is expensive or a View
is not always needed); however, do not copy any existing code that adds child View
s in a ViewHierarchyChanged()
override, as such code is usually an artifact of misunderstanding the Views ownership model.
Avoid
Typical code in native_app_window_views.cc
that sets up a child View
in ViewHierarchyChanged()
:
Best practice
Rewriting to do this at construction/Init()
makes |web_view_
|'s lifetime easier to reason about:
void NativeAppWindowViews::ViewHierarchyChanged( const views::ViewHierarchyChangedDetails& details) { if (details.is_add && details.child == this) { DCHECK(!web_view_); web_view_ = AddChildView( std::make_unique<views::WebView>(nullptr)); web_view_->SetWebContents( app_window_->web_contents()); } }
NativeAppWindowViews::NativeAppWindowViews() { ... web_view_ = AddChildView( std::make_unique<views::WebView>(nullptr)); } void NativeAppWindowViews::Init( extensions::AppWindow* app_window, const extensions::AppWindow::CreateParams& create_params) { app_window_ = app_window; web_view_->SetWebContents( app_window_->web_contents()); ... }