0

Renders cards in-process.

To be followed up with CL rendering cards in Mash.
See bug for before/after.

Codepath:
1) AssistantBubbleView receives card event.
2) AssistantBubbleView requests rendering from AshAssistantController.
3) AshAssitantController requests rendering from AssistantCardRenderer.
4) AssistantCardRenderer renders card and fires callback with token.
5) AssistantBubbleView uses token to embed the view.
6) When card is no longer needed, AssistantBubbleView requests release.

Note: There is some overlap between this CL and
https://chromium-review.googlesource.com/c/chromium/src/+/1012197 in
passing the controller to AssistantBubbleView.

Bug: b:78078693
Change-Id: I538b6c24fd72aba6ea2d263576c2ead9630cbc83
Reviewed-on: https://chromium-review.googlesource.com/1011460
Commit-Queue: David Black <dmblack@google.com>
Reviewed-by: Xiyuan Xia <xiyuan@chromium.org>
Reviewed-by: Xiaohui Chen <xiaohuic@chromium.org>
Reviewed-by: Daniel Cheng <dcheng@chromium.org>
Cr-Commit-Position: refs/heads/master@{#552130}
This commit is contained in:
David Black
2018-04-19 19:55:08 +00:00
committed by Commit Bot
parent e57301731d
commit e1a4b9edab
14 changed files with 434 additions and 14 deletions

@ -4,8 +4,10 @@
#include "ash/assistant/ash_assistant_controller.h"
#include "ash/session/session_controller.h"
#include "ash/shell.h"
#include "ash/shell_delegate.h"
#include "base/unguessable_token.h"
#include "ui/app_list/assistant_interaction_model_observer.h"
namespace ash {
@ -38,6 +40,37 @@ void AshAssistantController::SetAssistant(
assistant_->AddAssistantEventSubscriber(std::move(ptr));
}
void AshAssistantController::SetAssistantCardRenderer(
mojom::AssistantCardRendererPtr assistant_card_renderer) {
assistant_card_renderer_ = std::move(assistant_card_renderer);
}
void AshAssistantController::RenderCard(
const base::UnguessableToken& id_token,
mojom::AssistantCardParamsPtr params,
mojom::AssistantCardRenderer::RenderCallback callback) {
DCHECK(assistant_card_renderer_);
const mojom::UserSession* user_session =
Shell::Get()->session_controller()->GetUserSession(0);
if (!user_session) {
LOG(WARNING) << "Unable to retrieve active user session.";
return;
}
AccountId account_id = user_session->user_info->account_id;
assistant_card_renderer_->Render(account_id, id_token, std::move(params),
std::move(callback));
}
void AshAssistantController::ReleaseCard(
const base::UnguessableToken& id_token) {
DCHECK(assistant_card_renderer_);
assistant_card_renderer_->Release(id_token);
}
void AshAssistantController::AddInteractionModelObserver(
app_list::AssistantInteractionModelObserver* observer) {
assistant_interaction_model_.AddObserver(observer);

@ -5,8 +5,11 @@
#ifndef ASH_ASSISTANT_ASH_ASSISTANT_CONTROLLER_H_
#define ASH_ASSISTANT_ASH_ASSISTANT_CONTROLLER_H_
#include <string>
#include "ash/assistant/model/assistant_interaction_model_impl.h"
#include "ash/public/interfaces/ash_assistant_controller.mojom.h"
#include "ash/public/interfaces/assistant_card_renderer.mojom.h"
#include "ash/shell_observer.h"
#include "base/macros.h"
#include "chromeos/services/assistant/public/mojom/assistant.mojom.h"
@ -21,6 +24,10 @@ namespace aura {
class Window;
} // namespace aura
namespace base {
class UnguessableToken;
} // namespace base
namespace ash {
class AshAssistantController
@ -39,6 +46,11 @@ class AshAssistantController
app_list::AssistantInteractionModelObserver* observer) override;
void RemoveInteractionModelObserver(
app_list::AssistantInteractionModelObserver* observer) override;
void RenderCard(
const base::UnguessableToken& id_token,
mojom::AssistantCardParamsPtr params,
mojom::AssistantCardRenderer::RenderCallback callback) override;
void ReleaseCard(const base::UnguessableToken& id_token) override;
void OnSuggestionChipPressed(const std::string& text) override;
// chromeos::assistant::mojom::AssistantEventSubscriber:
@ -61,6 +73,8 @@ class AshAssistantController
// mojom::AshAssistantController:
void SetAssistant(
chromeos::assistant::mojom::AssistantPtr assistant) override;
void SetAssistantCardRenderer(
mojom::AssistantCardRendererPtr assistant_card_renderer) override;
// ShellObserver:
void OnAppListVisibilityChanged(bool shown,
@ -72,8 +86,8 @@ class AshAssistantController
assistant_event_subscriber_binding_;
AssistantInteractionModelImpl assistant_interaction_model_;
// Interface to Chrome OS Assistant service.
chromeos::assistant::mojom::AssistantPtr assistant_;
mojom::AssistantCardRendererPtr assistant_card_renderer_;
// TODO(b/77637813): Remove when pulling Assistant out of launcher.
bool is_app_list_shown_ = false;

@ -19,6 +19,7 @@ mojom("interfaces_internal") {
"ash_assistant_controller.mojom",
"ash_display_controller.mojom",
"ash_message_center_controller.mojom",
"assistant_card_renderer.mojom",
"cast_config.mojom",
"constants.mojom",
"docked_magnifier_controller.mojom",

@ -4,6 +4,7 @@
module ash.mojom;
import "ash/public/interfaces/assistant_card_renderer.mojom";
import "chromeos/services/assistant/public/mojom/assistant.mojom";
// Interface for AssistantManagerService to communicate with
@ -11,4 +12,8 @@ import "chromeos/services/assistant/public/mojom/assistant.mojom";
interface AshAssistantController {
// Provides a reference to the underlying service |assistant|.
SetAssistant(chromeos.assistant.mojom.Assistant assistant);
// Provides an interface to the |assistant_card_renderer| owned by
// AssistantClient.
SetAssistantCardRenderer(AssistantCardRenderer assistant_card_renderer);
};

@ -0,0 +1,36 @@
// Copyright 2018 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.
module ash.mojom;
import "components/signin/public/interfaces/account_id.mojom";
import "mojo/public/mojom/base/unguessable_token.mojom";
// Interface for a class which is responsible for rendering Assistant cards.
interface AssistantCardRenderer {
// Renders a card as defined by the specified |params| for the profile
// associated with |account_id|. Any call to Render should be paired with a
// corresponding call to Release when the card is no longer needed. To this
// end, the caller must supply a identifier by which to uniquely identify a
// card. When the card is ready for embedding, the supplied callback will be
// run providing an unguessable |embed_token| which corresponds uniquely to
// the rendered contents.
Render(signin.mojom.AccountId account_id,
mojo_base.mojom.UnguessableToken id_token, AssistantCardParams params)
=> (mojo_base.mojom.UnguessableToken embed_token);
// Releases any resources associated with card uniquely identified by
// |id_token|.
Release(mojo_base.mojom.UnguessableToken id_token);
};
// Defines parameters for an Assistant card.
struct AssistantCardParams {
// An HTML data string for the card.
string html;
// The minimum desired width for the card.
int32 min_width_dip;
// The maximum desired width for the card.
int32 max_width_dip;
};

@ -1719,11 +1719,14 @@ source_set("chromeos") {
if (enable_cros_assistant) {
deps += [
"//ash/public/cpp",
"//chromeos/services/assistant:lib",
"//chromeos/services/assistant/public/mojom",
]
sources += [
"assistant/assistant_card_renderer.cc",
"assistant/assistant_card_renderer.h",
"assistant/assistant_client.cc",
"assistant/assistant_client.h",
"assistant/platform_audio_input_host.cc",

@ -2,6 +2,7 @@ include_rules = [
# Add browser dependencies explicitly.
"-chrome",
"+chrome/browser/chromeos/assistant",
"+chrome/browser/chromeos/profiles/profile_helper.h",
"-chromeos/services",
"+chromeos/services/assistant/public",
]

@ -0,0 +1,159 @@
// Copyright 2018 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/chromeos/assistant/assistant_card_renderer.h"
#include <memory>
#include "ash/public/interfaces/ash_assistant_controller.mojom.h"
#include "ash/public/interfaces/constants.mojom.h"
#include "base/optional.h"
#include "chrome/browser/chromeos/profiles/profile_helper.h"
#include "components/user_manager/user_manager.h"
#include "content/public/browser/browser_context.h"
#include "content/public/browser/render_widget_host_view.h"
#include "content/public/browser/web_contents.h"
#include "content/public/browser/web_contents_observer.h"
#include "services/service_manager/public/cpp/connector.h"
#include "ui/app_list/answer_card_contents_registry.h"
#include "ui/views/controls/webview/web_contents_set_background_color.h"
#include "ui/views/controls/webview/webview.h"
#include "ui/views/view.h"
namespace chromeos {
namespace assistant {
namespace {
constexpr char kDataUriPrefix[] = "data:text/html,";
// AssistantCard ---------------------------------------------------------------
class AssistantCard : public content::WebContentsDelegate,
public content::WebContentsObserver {
public:
AssistantCard(const AccountId& account_id,
ash::mojom::AssistantCardParamsPtr params,
ash::mojom::AssistantCardRenderer::RenderCallback callback) {
const user_manager::User* user =
user_manager::UserManager::Get()->FindUser(account_id);
if (!user) {
LOG(WARNING) << "Unable to retrieve user for account_id.";
return;
}
Profile* profile = chromeos::ProfileHelper::Get()->GetProfileByUser(user);
if (!profile) {
LOG(WARNING) << "Unable to retrieve profile for user.";
return;
}
InitWebContents(profile, std::move(params));
HandleWebContents(profile, std::move(callback));
}
~AssistantCard() override {
web_contents_->SetDelegate(nullptr);
// When cards are rendered in the same process as ash, we need to release
// the associated view registered in the AnswerCardContentsRegistry's
// token-to-view map.
if (app_list::AnswerCardContentsRegistry::Get() && embed_token_.has_value())
app_list::AnswerCardContentsRegistry::Get()->Unregister(
embed_token_.value());
}
// content::WebContentsDelegate:
void ResizeDueToAutoResize(content::WebContents* web_contents,
const gfx::Size& new_size) override {
web_view_->SetPreferredSize(new_size);
}
private:
void InitWebContents(Profile* profile,
ash::mojom::AssistantCardParamsPtr params) {
web_contents_.reset(
content::WebContents::Create(content::WebContents::CreateParams(
profile, content::SiteInstance::Create(profile))));
// Use a transparent background.
views::WebContentsSetBackgroundColor::CreateForWebContentsWithColor(
web_contents_.get(), SK_ColorTRANSPARENT);
Observe(web_contents_.get());
web_contents_->SetDelegate(this);
// Load the card's HTML data string into the web contents.
content::NavigationController::LoadURLParams load_params(
GURL(kDataUriPrefix + params->html));
load_params.should_clear_history_list = true;
load_params.transition_type = ui::PAGE_TRANSITION_AUTO_TOPLEVEL;
web_contents_->GetController().LoadURLWithParams(load_params);
// Enable auto-resizing, respecting the specified size parameters.
web_contents_->GetRenderWidgetHostView()->EnableAutoResize(
gfx::Size(params->min_width_dip, 0),
gfx::Size(params->max_width_dip, INT_MAX));
}
void HandleWebContents(
Profile* profile,
ash::mojom::AssistantCardRenderer::RenderCallback callback) {
// When rendering cards in the same process as ash, we register the view for
// the card with the AnswerCardContentsRegistry's token-to-view map. The
// token returned from the registry will uniquely identify the view.
if (app_list::AnswerCardContentsRegistry::Get()) {
web_view_ = std::make_unique<views::WebView>(profile);
web_view_->set_owned_by_client();
web_view_->SetResizeBackgroundColor(SK_ColorTRANSPARENT);
web_view_->SetWebContents(web_contents_.get());
embed_token_ = app_list::AnswerCardContentsRegistry::Get()->Register(
web_view_.get());
std::move(callback).Run(embed_token_.value());
}
// TODO(dmblack): Handle Mash case.
}
std::unique_ptr<content::WebContents> web_contents_;
std::unique_ptr<views::WebView> web_view_;
base::Optional<base::UnguessableToken> embed_token_;
DISALLOW_COPY_AND_ASSIGN(AssistantCard);
};
} // namespace
AssistantCardRenderer::AssistantCardRenderer(
service_manager::Connector* connector)
: assistant_controller_binding_(this) {
// Bind to the Assistant controller in ash.
ash::mojom::AshAssistantControllerPtr assistant_controller;
connector->BindInterface(ash::mojom::kServiceName, &assistant_controller);
ash::mojom::AssistantCardRendererPtr ptr;
assistant_controller_binding_.Bind(mojo::MakeRequest(&ptr));
assistant_controller->SetAssistantCardRenderer(std::move(ptr));
}
AssistantCardRenderer::~AssistantCardRenderer() = default;
void AssistantCardRenderer::Render(
const AccountId& account_id,
const base::UnguessableToken& id_token,
ash::mojom::AssistantCardParamsPtr params,
ash::mojom::AssistantCardRenderer::RenderCallback callback) {
DCHECK(assistant_cards_.count(id_token) == 0);
assistant_cards_[id_token] = std::make_unique<AssistantCard>(
account_id, std::move(params), std::move(callback));
}
void AssistantCardRenderer::Release(const base::UnguessableToken& id_token) {
assistant_cards_.erase(id_token);
}
} // namespace assistant
} // namespace chromeos

@ -0,0 +1,62 @@
// Copyright 2018 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.
#ifndef CHROME_BROWSER_CHROMEOS_ASSISTANT_ASSISTANT_CARD_RENDERER_H_
#define CHROME_BROWSER_CHROMEOS_ASSISTANT_ASSISTANT_CARD_RENDERER_H_
#include <unordered_map>
#include "ash/public/interfaces/assistant_card_renderer.mojom.h"
#include "base/macros.h"
#include "base/unguessable_token.h"
#include "mojo/public/cpp/bindings/binding.h"
class AccountId;
namespace service_manager {
class Connector;
} // namespace service_manager
namespace chromeos {
namespace assistant {
namespace {
class AssistantCard;
} // namespace
// AssistantCardRenderer is the class responsible for rendering Assistant cards
// and owning their associated resources in chrome/browser for views to embed.
// In order to ensure resources live only as long as is necessary, any call to
// Render should be paired with a corresponding call to Release when the card
// is no longer needed. As such, the caller of Render must provide an identifier
// by which to uniquely identify a card.
class AssistantCardRenderer : public ash::mojom::AssistantCardRenderer {
public:
explicit AssistantCardRenderer(service_manager::Connector* connector);
~AssistantCardRenderer() override;
// ash::mojom::AssistantCardRenderer:
void Render(
const AccountId& account_id,
const base::UnguessableToken& id_token,
ash::mojom::AssistantCardParamsPtr params,
ash::mojom::AssistantCardRenderer::RenderCallback callback) override;
void Release(const base::UnguessableToken& id_token) override;
private:
mojo::Binding<ash::mojom::AssistantCardRenderer>
assistant_controller_binding_;
std::unordered_map<base::UnguessableToken,
std::unique_ptr<AssistantCard>,
base::UnguessableTokenHash>
assistant_cards_;
DISALLOW_COPY_AND_ASSIGN(AssistantCardRenderer);
};
} // namespace assistant
} // namespace chromeos
#endif // CHROME_BROWSER_CHROMEOS_ASSISTANT_ASSISTANT_CARD_RENDERER_H_

@ -6,6 +6,7 @@
#include <utility>
#include "chrome/browser/chromeos/assistant/assistant_card_renderer.h"
#include "chromeos/services/assistant/public/mojom/constants.mojom.h"
#include "services/service_manager/public/cpp/connector.h"
@ -44,6 +45,8 @@ void AssistantClient::Start(service_manager::Connector* connector) {
assistant_connection_->Init(std::move(client_ptr),
std::move(audio_input_ptr));
assistant_card_renderer_.reset(new AssistantCardRenderer(connector));
}
void AssistantClient::OnAssistantStatusChanged(bool running) {

@ -5,6 +5,8 @@
#ifndef CHROME_BROWSER_CHROMEOS_ASSISTANT_ASSISTANT_CLIENT_H_
#define CHROME_BROWSER_CHROMEOS_ASSISTANT_ASSISTANT_CLIENT_H_
#include <memory>
#include "base/macros.h"
#include "chrome/browser/chromeos/assistant/platform_audio_input_host.h"
#include "chromeos/services/assistant/public/mojom/assistant.mojom.h"
@ -17,6 +19,8 @@ class Connector;
namespace chromeos {
namespace assistant {
class AssistantCardRenderer;
// Class to handle all assistant in-browser-process functionalities.
class AssistantClient : mojom::Client {
public:
@ -39,6 +43,8 @@ class AssistantClient : mojom::Client {
PlatformAudioInputHost audio_input_;
bool running_ = false;
std::unique_ptr<AssistantCardRenderer> assistant_card_renderer_;
DISALLOW_COPY_AND_ASSIGN(AssistantClient);
};

@ -7,20 +7,41 @@
#include <string>
#include "ash/public/interfaces/assistant_card_renderer.mojom.h"
#include "base/macros.h"
namespace base {
class UnguessableToken;
} // namespace base
namespace app_list {
class AssistantInteractionModelObserver;
// Interface for the Assistant controller in ash.
class AssistantController {
public:
// Adds/removes the specified interaction |observer|.
// Registers the specified |observer| with the interaction model observer
// pool.
virtual void AddInteractionModelObserver(
AssistantInteractionModelObserver* observer) = 0;
// Unregisters the specified |observer| from the interaction model observer
// pool.
virtual void RemoveInteractionModelObserver(
AssistantInteractionModelObserver* observer) = 0;
// Renders a card, uniquely identified by |id_token|, according to the
// specified |params|. When the card is ready for embedding, the supplied
// |callback| is run with a token for embedding.
virtual void RenderCard(
const base::UnguessableToken& id_token,
ash::mojom::AssistantCardParamsPtr params,
ash::mojom::AssistantCardRenderer::RenderCallback callback) = 0;
// Releases resources for the card uniquely identified by |id_token|.
virtual void ReleaseCard(const base::UnguessableToken& id_token) = 0;
// Invoked on suggestion chip pressed event.
virtual void OnSuggestionChipPressed(const std::string& text) = 0;

@ -4,7 +4,10 @@
#include "ui/app_list/views/assistant_bubble_view.h"
#include "base/callback.h"
#include "base/strings/utf_string_conversions.h"
#include "base/unguessable_token.h"
#include "ui/app_list/answer_card_contents_registry.h"
#include "ui/app_list/assistant_controller.h"
#include "ui/app_list/assistant_interaction_model.h"
#include "ui/app_list/views/suggestion_chip_view.h"
@ -30,8 +33,6 @@ constexpr SkColor kTextColorPrimary = SkColorSetA(SK_ColorBLACK, 0xDE);
// TODO(dmblack): Remove after removing placeholders.
// Placeholder.
constexpr int kPlaceholderCardPreferredHeightDip = 200;
constexpr int kPlaceholderCardCornerRadiusDip = 4;
constexpr SkColor kPlaceholderColor = SkColorSetA(SK_ColorBLACK, 0x1F);
constexpr int kPlaceholderIconSizeDip = 32;
@ -162,6 +163,39 @@ class TextContainer : public views::View {
DISALLOW_COPY_AND_ASSIGN(TextContainer);
};
// CardContainer ---------------------------------------------------------------
class CardContainer : public views::View {
public:
CardContainer() { InitLayout(); }
~CardContainer() override = default;
// views::View:
void ChildPreferredSizeChanged(views::View* child) override {
PreferredSizeChanged();
}
void EmbedCard(const base::UnguessableToken& embed_token) {
// When the card has been rendered in the same process, its view is
// available in the AnswerCardContentsRegistry's token-to-view map.
if (AnswerCardContentsRegistry::Get()) {
AddChildView(AnswerCardContentsRegistry::Get()->GetView(embed_token));
}
// TODO(dmblack): Handle Mash case.
}
void UnembedCard() { RemoveAllChildViews(true); }
private:
void InitLayout() {
SetLayoutManager(std::make_unique<views::BoxLayout>(
views::BoxLayout::Orientation::kVertical));
}
DISALLOW_COPY_AND_ASSIGN(CardContainer);
};
// TODO(dmblack): Container should wrap chips in a horizontal scroll view.
// SuggestionsContainer --------------------------------------------------------
@ -242,8 +276,9 @@ AssistantBubbleView::AssistantBubbleView(
: assistant_controller_(assistant_controller),
interaction_container_(new InteractionContainer()),
text_container_(new TextContainer()),
card_container_(new views::View()),
suggestions_container_(new SuggestionsContainer(this)) {
card_container_(new CardContainer()),
suggestions_container_(new SuggestionsContainer(this)),
weak_factory_(this) {
InitLayout();
// Observe changes to interaction model.
@ -253,6 +288,7 @@ AssistantBubbleView::AssistantBubbleView(
AssistantBubbleView::~AssistantBubbleView() {
assistant_controller_->RemoveInteractionModelObserver(this);
OnReleaseCard();
}
gfx::Size AssistantBubbleView::CalculatePreferredSize() const {
@ -286,12 +322,6 @@ void AssistantBubbleView::InitLayout() {
// Card container.
card_container_->SetVisible(false);
card_container_->SetBackground(std::make_unique<RoundRectBackground>(
kPlaceholderColor, kPlaceholderCardCornerRadiusDip));
card_container_->SetBorder(
views::CreateEmptyBorder(gfx::Insets(0, kPaddingDip)));
card_container_->SetPreferredSize(
gfx::Size(INT_MAX, kPlaceholderCardPreferredHeightDip));
AddChildView(card_container_);
// Suggestions container.
@ -300,12 +330,47 @@ void AssistantBubbleView::InitLayout() {
}
void AssistantBubbleView::OnCardChanged(const std::string& html) {
// TODO(dmblack): Handle HTML.
// Clear the previous card.
OnCardCleared();
// Generate a unique identifier for the card. This will be used to clean up
// card resources when it is no longer needed.
id_token_ = base::UnguessableToken::Create();
// Configure parameters for the card.
ash::mojom::AssistantCardParamsPtr params(
ash::mojom::AssistantCardParams::New());
params->html = html;
params->min_width_dip = kPreferredWidthDip;
params->max_width_dip = kPreferredWidthDip;
// The card will be rendered by AssistantCardRenderer, running the specified
// callback when the card is ready for embedding.
assistant_controller_->RenderCard(
id_token_.value(), std::move(params),
base::BindOnce(&AssistantBubbleView::OnCardReady,
weak_factory_.GetWeakPtr()));
}
void AssistantBubbleView::OnCardReady(
const base::UnguessableToken& embed_token) {
card_container_->EmbedCard(embed_token);
card_container_->SetVisible(true);
}
void AssistantBubbleView::OnCardCleared() {
card_container_->SetVisible(false);
card_container_->UnembedCard();
OnReleaseCard();
}
void AssistantBubbleView::OnReleaseCard() {
if (id_token_) {
// Release any resources associated with the card identified by |id_token_|
// owned by AssistantCardRenderer.
assistant_controller_->ReleaseCard(id_token_.value());
id_token_.reset();
}
}
void AssistantBubbleView::OnQueryChanged(const Query& query) {

@ -5,10 +5,12 @@
#ifndef UI_APP_LIST_VIEWS_ASSISTANT_BUBBLE_VIEW_H_
#define UI_APP_LIST_VIEWS_ASSISTANT_BUBBLE_VIEW_H_
#include <memory>
#include <string>
#include <vector>
#include "base/macros.h"
#include "base/optional.h"
#include "ui/app_list/assistant_interaction_model_observer.h"
#include "ui/app_list/views/suggestion_chip_view.h"
#include "ui/views/view.h"
@ -18,6 +20,7 @@ namespace app_list {
class AssistantController;
namespace {
class CardContainer;
class InteractionContainer;
class SuggestionsContainer;
class TextContainer;
@ -52,12 +55,20 @@ class AssistantBubbleView : public views::View,
private:
void InitLayout();
void OnCardReady(const base::UnguessableToken& embed_token);
void OnReleaseCard();
AssistantController* assistant_controller_; // Owned by Shell.
InteractionContainer* interaction_container_; // Owned by view hierarchy.
TextContainer* text_container_; // Owned by view hierarchy.
views::View* card_container_; // Owned by view hierarchy.
CardContainer* card_container_; // Owned by view hierarchy.
SuggestionsContainer* suggestions_container_; // Owned by view hierarchy.
// Uniquely identifies a card owned by AssistantCardRenderer.
base::Optional<base::UnguessableToken> id_token_;
base::WeakPtrFactory<AssistantBubbleView> weak_factory_;
DISALLOW_COPY_AND_ASSIGN(AssistantBubbleView);
};