tray_bubble_view.cc 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562
  1. // Copyright (c) 2012 The Chromium Authors. All rights reserved.
  2. // Use of this source code is governed by a BSD-style license that can be
  3. // found in the LICENSE file.
  4. #include "ash/system/tray/tray_bubble_view.h"
  5. #include <algorithm>
  6. #include <memory>
  7. #include <numeric>
  8. #include "ash/accelerators/accelerator_controller_impl.h"
  9. #include "ash/accessibility/accessibility_controller_impl.h"
  10. #include "ash/bubble/bubble_constants.h"
  11. #include "ash/constants/ash_features.h"
  12. #include "ash/public/cpp/accelerators.h"
  13. #include "ash/public/cpp/style/color_provider.h"
  14. #include "ash/shell.h"
  15. #include "ash/style/ash_color_id.h"
  16. #include "ash/style/system_shadow.h"
  17. #include "ash/system/tray/tray_constants.h"
  18. #include "ash/system/unified/unified_system_tray_view.h"
  19. #include "third_party/skia/include/core/SkCanvas.h"
  20. #include "third_party/skia/include/core/SkColor.h"
  21. #include "third_party/skia/include/core/SkPath.h"
  22. #include "ui/accessibility/ax_enums.mojom.h"
  23. #include "ui/accessibility/ax_node_data.h"
  24. #include "ui/aura/env.h"
  25. #include "ui/aura/window.h"
  26. #include "ui/base/accelerators/accelerator.h"
  27. #include "ui/base/metadata/metadata_impl_macros.h"
  28. #include "ui/compositor/layer.h"
  29. #include "ui/compositor/layer_type.h"
  30. #include "ui/compositor_extra/shadow.h"
  31. #include "ui/events/event.h"
  32. #include "ui/gfx/canvas.h"
  33. #include "ui/gfx/color_palette.h"
  34. #include "ui/gfx/geometry/insets.h"
  35. #include "ui/gfx/geometry/rect.h"
  36. #include "ui/gfx/geometry/skia_conversions.h"
  37. #include "ui/views/bubble/bubble_frame_view.h"
  38. #include "ui/views/highlight_border.h"
  39. #include "ui/views/layout/box_layout.h"
  40. #include "ui/views/painter.h"
  41. #include "ui/views/views_delegate.h"
  42. #include "ui/wm/core/shadow_types.h"
  43. #include "ui/wm/core/window_util.h"
  44. using views::BubbleBorder;
  45. using views::BubbleFrameView;
  46. using views::NonClientFrameView;
  47. using views::View;
  48. using views::ViewsDelegate;
  49. using views::Widget;
  50. namespace ash {
  51. namespace {
  52. BubbleBorder::Arrow GetArrowAlignment(ash::ShelfAlignment alignment) {
  53. // The tray bubble is in a corner. In this case, we want the arrow to be
  54. // flush with one side instead of centered on the bubble.
  55. switch (alignment) {
  56. case ash::ShelfAlignment::kBottom:
  57. case ash::ShelfAlignment::kBottomLocked:
  58. return base::i18n::IsRTL() ? BubbleBorder::BOTTOM_LEFT
  59. : BubbleBorder::BOTTOM_RIGHT;
  60. case ash::ShelfAlignment::kLeft:
  61. return BubbleBorder::LEFT_BOTTOM;
  62. case ash::ShelfAlignment::kRight:
  63. return BubbleBorder::RIGHT_BOTTOM;
  64. }
  65. }
  66. // Detects any mouse movement. This is needed to detect mouse movements by the
  67. // user over the bubble if the bubble got created underneath the cursor.
  68. class MouseMoveDetectorHost : public views::MouseWatcherHost {
  69. public:
  70. MouseMoveDetectorHost();
  71. MouseMoveDetectorHost(const MouseMoveDetectorHost&) = delete;
  72. MouseMoveDetectorHost& operator=(const MouseMoveDetectorHost&) = delete;
  73. ~MouseMoveDetectorHost() override;
  74. bool Contains(const gfx::Point& screen_point, EventType type) override;
  75. };
  76. MouseMoveDetectorHost::MouseMoveDetectorHost() {}
  77. MouseMoveDetectorHost::~MouseMoveDetectorHost() {}
  78. bool MouseMoveDetectorHost::Contains(const gfx::Point& screen_point,
  79. EventType type) {
  80. return false;
  81. }
  82. // Custom layout for the bubble-view. Does the default box-layout if there is
  83. // enough height. Otherwise, makes sure the bottom rows are visible.
  84. class BottomAlignedBoxLayout : public views::BoxLayout {
  85. public:
  86. explicit BottomAlignedBoxLayout(TrayBubbleView* bubble_view)
  87. : BoxLayout(BoxLayout::Orientation::kVertical),
  88. bubble_view_(bubble_view) {}
  89. BottomAlignedBoxLayout(const BottomAlignedBoxLayout&) = delete;
  90. BottomAlignedBoxLayout& operator=(const BottomAlignedBoxLayout&) = delete;
  91. ~BottomAlignedBoxLayout() override {}
  92. private:
  93. void Layout(View* host) override {
  94. if (host->height() >= host->GetPreferredSize().height() ||
  95. !bubble_view_->is_gesture_dragging()) {
  96. BoxLayout::Layout(host);
  97. return;
  98. }
  99. int consumed_height = 0;
  100. for (auto i = host->children().rbegin();
  101. i != host->children().rend() && consumed_height < host->height();
  102. ++i) {
  103. View* child = *i;
  104. if (!child->GetVisible())
  105. continue;
  106. gfx::Size size = child->GetPreferredSize();
  107. child->SetBounds(0, host->height() - consumed_height - size.height(),
  108. host->width(), size.height());
  109. consumed_height += size.height();
  110. }
  111. }
  112. TrayBubbleView* bubble_view_;
  113. };
  114. } // namespace
  115. TrayBubbleView::Delegate::Delegate() = default;
  116. TrayBubbleView::Delegate::~Delegate() = default;
  117. void TrayBubbleView::Delegate::BubbleViewDestroyed() {}
  118. void TrayBubbleView::Delegate::OnMouseEnteredView() {}
  119. void TrayBubbleView::Delegate::OnMouseExitedView() {}
  120. std::u16string TrayBubbleView::Delegate::GetAccessibleNameForBubble() {
  121. return std::u16string();
  122. }
  123. bool TrayBubbleView::Delegate::ShouldEnableExtraKeyboardAccessibility() {
  124. return false;
  125. }
  126. void TrayBubbleView::Delegate::HideBubble(const TrayBubbleView* bubble_view) {}
  127. base::WeakPtr<TrayBubbleView::Delegate> TrayBubbleView::Delegate::GetWeakPtr() {
  128. return weak_ptr_factory_.GetWeakPtr();
  129. }
  130. absl::optional<AcceleratorAction>
  131. TrayBubbleView::Delegate::GetAcceleratorAction() const {
  132. // TODO(crbug/1234891) Make this a pure virtual function so all
  133. // bubble delegates need to specify accelerator actions.
  134. return absl::nullopt;
  135. }
  136. TrayBubbleView::InitParams::InitParams() = default;
  137. TrayBubbleView::InitParams::~InitParams() = default;
  138. TrayBubbleView::InitParams::InitParams(const InitParams& other) = default;
  139. TrayBubbleView::RerouteEventHandler::RerouteEventHandler(
  140. TrayBubbleView* tray_bubble_view)
  141. : tray_bubble_view_(tray_bubble_view) {
  142. aura::Env::GetInstance()->AddPreTargetHandler(
  143. this, ui::EventTarget::Priority::kSystem);
  144. }
  145. TrayBubbleView::RerouteEventHandler::~RerouteEventHandler() {
  146. aura::Env::GetInstance()->RemovePreTargetHandler(this);
  147. }
  148. void TrayBubbleView::RerouteEventHandler::OnKeyEvent(ui::KeyEvent* event) {
  149. // Do not handle a key event if it is targeted to the tray or its descendants,
  150. // or if the target has the tray as a transient ancestor. RerouteEventHandler
  151. // is for rerouting events which are not targetted to the tray. Those events
  152. // should be handled by the target.
  153. aura::Window* target = static_cast<aura::Window*>(event->target());
  154. aura::Window* tray_window = tray_bubble_view_->GetWidget()->GetNativeView();
  155. if (target && (tray_window->Contains(target) ||
  156. wm::HasTransientAncestor(target, tray_window))) {
  157. return;
  158. }
  159. // Only passes Tab, Shift+Tab, Esc to the widget as it can consume more key
  160. // events. e.g. Alt+Tab can be consumed as focus traversal by FocusManager.
  161. ui::KeyboardCode key_code = event->key_code();
  162. int flags = event->flags() &
  163. (ui::EF_SHIFT_DOWN | ui::EF_CONTROL_DOWN | ui::EF_ALT_DOWN |
  164. ui::EF_COMMAND_DOWN | ui::EF_ALTGR_DOWN | ui::EF_MOD3_DOWN);
  165. if ((key_code == ui::VKEY_TAB && flags == ui::EF_NONE) ||
  166. (key_code == ui::VKEY_TAB && flags == ui::EF_SHIFT_DOWN) ||
  167. (key_code == ui::VKEY_ESCAPE && flags == ui::EF_NONE) ||
  168. // Do not dismiss the bubble immediately when a user triggers a feedback
  169. // report; if they're reporting an issue with the bubble we want the
  170. // screenshot to contain it.
  171. (key_code == ui::VKEY_I &&
  172. flags == (ui::EF_ALT_DOWN | ui::EF_SHIFT_DOWN))) {
  173. // Make TrayBubbleView activatable as the following Widget::OnKeyEvent might
  174. // try to activate it.
  175. tray_bubble_view_->SetCanActivate(true);
  176. tray_bubble_view_->GetWidget()->OnKeyEvent(event);
  177. if (event->handled())
  178. return;
  179. }
  180. // Always consumes key event not to pass it to other widgets. Calling
  181. // StopPropagation here to make this consistent with
  182. // MenuController::OnWillDispatchKeyEvent.
  183. event->StopPropagation();
  184. // To provide consistent behavior with a menu, process accelerator as a menu
  185. // is open if the event is not handled by the widget.
  186. ui::Accelerator accelerator(*event);
  187. // crbug/1212857 Immediately close the bubble if the accelerator action
  188. // is going to do it and do not process the accelerator. If the accelerator
  189. // action is executed asynchronously it will execute after the bubble has
  190. // already been closed and it will result in the accelerator action reopening
  191. // the bubble.
  192. if (tray_bubble_view_->GetAcceleratorAction().has_value() &&
  193. AcceleratorControllerImpl::Get()->DoesAcceleratorMatchAction(
  194. ui::Accelerator(*event),
  195. tray_bubble_view_->GetAcceleratorAction().value())) {
  196. tray_bubble_view_->CloseBubbleView();
  197. } else {
  198. ViewsDelegate::GetInstance()->ProcessAcceleratorWhileMenuShowing(
  199. accelerator);
  200. }
  201. }
  202. TrayBubbleView::TrayBubbleView(const InitParams& init_params)
  203. : BubbleDialogDelegateView(init_params.anchor_view,
  204. GetArrowAlignment(init_params.shelf_alignment)),
  205. params_(init_params),
  206. layout_(nullptr),
  207. delegate_(init_params.delegate),
  208. preferred_width_(init_params.preferred_width),
  209. is_gesture_dragging_(false),
  210. mouse_actively_entered_(false) {
  211. // We set the dialog role because views::BubbleDialogDelegate defaults this to
  212. // an alert dialog. This would make screen readers announce the whole of the
  213. // system tray which is undesirable.
  214. SetAccessibleRole(ax::mojom::Role::kDialog);
  215. // We force to create contents background since the bubble border background
  216. // is not shown in this view.
  217. if (features::IsDarkLightModeEnabled())
  218. set_force_create_contents_background(true);
  219. // Bubbles that use transparent colors should not paint their ClientViews to a
  220. // layer as doing so could result in visual artifacts.
  221. SetPaintClientToLayer(false);
  222. SetButtons(ui::DIALOG_BUTTON_NONE);
  223. DCHECK(delegate_);
  224. DCHECK(params_.parent_window);
  225. // anchor_widget() is computed by BubbleDialogDelegateView().
  226. DCHECK((init_params.anchor_mode != TrayBubbleView::AnchorMode::kView) ||
  227. anchor_widget());
  228. set_parent_window(params_.parent_window);
  229. AccessibilityControllerImpl* controller =
  230. Shell::Get()->accessibility_controller();
  231. SetCanActivate(controller->spoken_feedback().enabled() ||
  232. controller->dictation().enabled());
  233. SetNotifyEnterExitOnChild(true);
  234. set_close_on_deactivate(init_params.close_on_deactivate);
  235. set_margins(init_params.margin.has_value() ? init_params.margin.value()
  236. : gfx::Insets());
  237. if (init_params.translucent) {
  238. // TODO(crbug/1313073): In the dark light mode feature, remove layer
  239. // creation in children views of this view to improve performance.
  240. SetPaintToLayer(features::IsDarkLightModeEnabled() ? ui::LAYER_TEXTURED
  241. : ui::LAYER_SOLID_COLOR);
  242. layer()->SetFillsBoundsOpaquely(false);
  243. layer()->SetRoundedCornerRadius(
  244. gfx::RoundedCornersF{static_cast<float>(params_.corner_radius)});
  245. layer()->SetIsFastRoundedCorner(true);
  246. layer()->SetBackgroundBlur(ColorProvider::kBackgroundBlurSigma);
  247. layer()->SetBackdropFilterQuality(ColorProvider::kBackgroundBlurQuality);
  248. } else {
  249. // Create a layer so that the layer for FocusRing stays in this view's
  250. // layer. Without it, the layer for FocusRing goes above the
  251. // NativeViewHost and may steal events.
  252. SetPaintToLayer(ui::LAYER_NOT_DRAWN);
  253. if (features::IsDarkLightModeEnabled() && !init_params.transparent) {
  254. SetPaintToLayer();
  255. layer()->SetRoundedCornerRadius(
  256. gfx::RoundedCornersF{static_cast<float>(params_.corner_radius)});
  257. }
  258. }
  259. if (init_params.transparent) {
  260. set_color(SK_ColorTRANSPARENT);
  261. }
  262. if (params_.has_shadow && features::IsSystemTrayShadowEnabled()) {
  263. shadow_ = SystemShadow::CreateShadowOnNinePatchLayerForView(
  264. this, params_.shadow_type);
  265. shadow_->SetRoundedCornerRadius(params_.corner_radius);
  266. }
  267. auto layout = std::make_unique<BottomAlignedBoxLayout>(this);
  268. layout->SetDefaultFlex(1);
  269. layout_ = SetLayoutManager(std::move(layout));
  270. if (init_params.anchor_mode == AnchorMode::kRect) {
  271. SetAnchorView(nullptr);
  272. SetAnchorRect(init_params.anchor_rect);
  273. }
  274. }
  275. TrayBubbleView::~TrayBubbleView() {
  276. mouse_watcher_.reset();
  277. if (delegate_) {
  278. // Inform host items (models) that their views are being destroyed.
  279. delegate_->BubbleViewDestroyed();
  280. }
  281. }
  282. void TrayBubbleView::InitializeAndShowBubble() {
  283. GetWidget()->Show();
  284. UpdateBubble();
  285. if (IsAnchoredToStatusArea()) {
  286. tray_bubble_counter_.emplace(
  287. StatusAreaWidget::ForWindow(GetWidget()->GetNativeView()));
  288. }
  289. // Register pre target event handler to reroute key
  290. // events to the widget for activating the view or closing it.
  291. if (!CanActivate() && params_.reroute_event_handler)
  292. reroute_event_handler_ = std::make_unique<RerouteEventHandler>(this);
  293. }
  294. void TrayBubbleView::UpdateBubble() {
  295. if (GetWidget()) {
  296. SizeToContents();
  297. GetWidget()->GetRootView()->SchedulePaint();
  298. }
  299. }
  300. void TrayBubbleView::SetMaxHeight(int height) {
  301. params_.max_height = height;
  302. if (GetWidget())
  303. SizeToContents();
  304. }
  305. void TrayBubbleView::SetBottomPadding(int padding) {
  306. layout_->set_inside_border_insets(gfx::Insets::TLBR(0, 0, padding, 0));
  307. }
  308. void TrayBubbleView::SetPreferredWidth(int width) {
  309. if (preferred_width_ == width)
  310. return;
  311. preferred_width_ = width;
  312. if (GetWidget())
  313. SizeToContents();
  314. }
  315. gfx::Insets TrayBubbleView::GetBorderInsets() const {
  316. auto* bubble_border = GetBubbleFrameView()->bubble_border();
  317. return bubble_border ? bubble_border->GetInsets() : gfx::Insets();
  318. }
  319. absl::optional<AcceleratorAction> TrayBubbleView::GetAcceleratorAction() const {
  320. return delegate_->GetAcceleratorAction();
  321. }
  322. void TrayBubbleView::ResetDelegate() {
  323. reroute_event_handler_.reset();
  324. delegate_ = nullptr;
  325. }
  326. void TrayBubbleView::ChangeAnchorView(views::View* anchor_view) {
  327. DCHECK_EQ(AnchorMode::kView, params_.anchor_mode);
  328. BubbleDialogDelegateView::SetAnchorView(anchor_view);
  329. }
  330. void TrayBubbleView::ChangeAnchorRect(const gfx::Rect& rect) {
  331. DCHECK_EQ(AnchorMode::kRect, params_.anchor_mode);
  332. BubbleDialogDelegateView::SetAnchorRect(rect);
  333. }
  334. void TrayBubbleView::ChangeAnchorAlignment(ShelfAlignment alignment) {
  335. SetArrow(GetArrowAlignment(alignment));
  336. }
  337. bool TrayBubbleView::IsAnchoredToStatusArea() const {
  338. return params_.is_anchored_to_status_area;
  339. }
  340. void TrayBubbleView::StopReroutingEvents() {
  341. reroute_event_handler_.reset();
  342. }
  343. void TrayBubbleView::OnWidgetClosing(Widget* widget) {
  344. // We no longer need to watch key events for activation if the widget is
  345. // closing.
  346. reroute_event_handler_.reset();
  347. BubbleDialogDelegateView::OnWidgetClosing(widget);
  348. if (IsAnchoredToStatusArea())
  349. tray_bubble_counter_.reset();
  350. }
  351. void TrayBubbleView::OnWidgetActivationChanged(Widget* widget, bool active) {
  352. // We no longer need to watch key events for activation if the widget is
  353. // activated.
  354. reroute_event_handler_.reset();
  355. BubbleDialogDelegateView::OnWidgetActivationChanged(widget, active);
  356. }
  357. ui::LayerType TrayBubbleView::GetLayerType() const {
  358. if (params_.translucent)
  359. return ui::LAYER_NOT_DRAWN;
  360. return ui::LAYER_TEXTURED;
  361. }
  362. std::unique_ptr<NonClientFrameView> TrayBubbleView::CreateNonClientFrameView(
  363. Widget* widget) {
  364. // Create the customized bubble border.
  365. std::unique_ptr<BubbleBorder> bubble_border =
  366. std::make_unique<BubbleBorder>(arrow(), BubbleBorder::NO_SHADOW);
  367. if (params_.corner_radius)
  368. bubble_border->SetCornerRadius(params_.corner_radius);
  369. bubble_border->set_avoid_shadow_overlap(true);
  370. if (params_.anchor_mode == AnchorMode::kRect && params_.insets.has_value())
  371. bubble_border->set_insets(params_.insets.value());
  372. auto frame = BubbleDialogDelegateView::CreateNonClientFrameView(widget);
  373. static_cast<BubbleFrameView*>(frame.get())
  374. ->SetBubbleBorder(std::move(bubble_border));
  375. return frame;
  376. }
  377. bool TrayBubbleView::WidgetHasHitTestMask() const {
  378. return true;
  379. }
  380. void TrayBubbleView::GetWidgetHitTestMask(SkPath* mask) const {
  381. DCHECK(mask);
  382. mask->addRect(gfx::RectToSkRect(GetBubbleFrameView()->GetContentsBounds()));
  383. }
  384. std::u16string TrayBubbleView::GetAccessibleWindowTitle() const {
  385. if (delegate_)
  386. return delegate_->GetAccessibleNameForBubble();
  387. else
  388. return std::u16string();
  389. }
  390. gfx::Size TrayBubbleView::CalculatePreferredSize() const {
  391. return gfx::Size(preferred_width_, GetHeightForWidth(preferred_width_));
  392. }
  393. int TrayBubbleView::GetHeightForWidth(int width) const {
  394. width = std::max(width - GetInsets().width(), 0);
  395. const auto visible_height = [width](int height, const views::View* child) {
  396. return height + (child->GetVisible() ? child->GetHeightForWidth(width) : 0);
  397. };
  398. const int height = std::accumulate(children().cbegin(), children().cend(),
  399. GetInsets().height(), visible_height);
  400. return (params_.max_height != 0) ? std::min(height, params_.max_height)
  401. : height;
  402. }
  403. void TrayBubbleView::OnMouseEntered(const ui::MouseEvent& event) {
  404. mouse_watcher_.reset();
  405. if (delegate_ && !(event.flags() & ui::EF_IS_SYNTHESIZED)) {
  406. // The user actively moved the mouse over the bubble; inform the delegate.
  407. delegate_->OnMouseEnteredView();
  408. mouse_actively_entered_ = true;
  409. } else {
  410. // The mouse was located over the bubble when it was first shown; use
  411. // MouseWatcher to wait for user interaction before signaling the delegate.
  412. mouse_watcher_ = std::make_unique<views::MouseWatcher>(
  413. std::make_unique<MouseMoveDetectorHost>(), this);
  414. mouse_watcher_->set_notify_on_exit_time(base::TimeDelta());
  415. mouse_watcher_->Start(GetWidget()->GetNativeWindow());
  416. }
  417. }
  418. void TrayBubbleView::OnMouseExited(const ui::MouseEvent& event) {
  419. // Disable any MouseWatcher waiting for user interaction inside the bubble.
  420. mouse_watcher_.reset();
  421. // Only notify the delegate on exit if it was notified on enter.
  422. if (delegate_ && mouse_actively_entered_)
  423. delegate_->OnMouseExitedView();
  424. }
  425. void TrayBubbleView::GetAccessibleNodeData(ui::AXNodeData* node_data) {
  426. if (delegate_ && CanActivate()) {
  427. node_data->role = ax::mojom::Role::kWindow;
  428. node_data->SetName(delegate_->GetAccessibleNameForBubble());
  429. }
  430. }
  431. void TrayBubbleView::OnThemeChanged() {
  432. views::BubbleDialogDelegateView::OnThemeChanged();
  433. if (params_.transparent)
  434. return;
  435. if (features::IsDarkLightModeEnabled()) {
  436. SetBorder(std::make_unique<views::HighlightBorder>(
  437. params_.corner_radius, views::HighlightBorder::Type::kHighlightBorder1,
  438. /*use_light_colors=*/false));
  439. set_color(GetColorProvider()->GetColor(kColorAshShieldAndBase80));
  440. return;
  441. }
  442. DCHECK(layer());
  443. if (layer()->type() != ui::LAYER_SOLID_COLOR)
  444. return;
  445. layer()->SetColor(GetColorProvider()->GetColor(kColorAshShieldAndBase80));
  446. }
  447. void TrayBubbleView::MouseMovedOutOfHost() {
  448. // The user moved the mouse that was over the bubble when it was first shown.
  449. if (delegate_)
  450. delegate_->OnMouseEnteredView();
  451. mouse_actively_entered_ = true;
  452. mouse_watcher_.reset();
  453. }
  454. void TrayBubbleView::ChildPreferredSizeChanged(View* child) {
  455. SizeToContents();
  456. }
  457. void TrayBubbleView::SetBubbleBorderInsets(gfx::Insets insets) {
  458. if (GetBubbleFrameView()->bubble_border())
  459. GetBubbleFrameView()->bubble_border()->set_insets(insets);
  460. }
  461. void TrayBubbleView::CloseBubbleView() {
  462. if (!delegate_)
  463. return;
  464. delegate_->HideBubble(this);
  465. }
  466. BEGIN_METADATA(TrayBubbleView, views::BubbleDialogDelegateView)
  467. END_METADATA
  468. } // namespace ash