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 
5 #include "chrome/browser/ui/views/location_bar/icon_label_bubble_view.h"
6 
7 #include <algorithm>
8 #include <memory>
9 #include <utility>
10 
11 #include "chrome/browser/ui/layout_constants.h"
12 #include "chrome/browser/ui/omnibox/omnibox_theme.h"
13 #include "ui/accessibility/ax_node_data.h"
14 #include "ui/base/l10n/l10n_util.h"
15 #include "ui/compositor/layer_animator.h"
16 #include "ui/compositor/scoped_layer_animation_settings.h"
17 #include "ui/gfx/canvas.h"
18 #include "ui/gfx/geometry/insets.h"
19 #include "ui/gfx/geometry/rect.h"
20 #include "ui/gfx/scoped_canvas.h"
21 #include "ui/gfx/skia_util.h"
22 #include "ui/views/accessibility/ax_virtual_view.h"
23 #include "ui/views/accessibility/view_accessibility.h"
24 #include "ui/views/animation/flood_fill_ink_drop_ripple.h"
25 #include "ui/views/animation/ink_drop_highlight.h"
26 #include "ui/views/animation/ink_drop_impl.h"
27 #include "ui/views/animation/ink_drop_mask.h"
28 #include "ui/views/animation/ink_drop_ripple.h"
29 #include "ui/views/background.h"
30 #include "ui/views/border.h"
31 #include "ui/views/controls/highlight_path_generator.h"
32 #include "ui/views/controls/image_view.h"
33 #include "ui/views/widget/widget.h"
34 
35 namespace {
36 
37 // Amount of space reserved for the separator that appears after the icon or
38 // label.
39 constexpr int kIconLabelBubbleSeparatorWidth = 1;
40 
41 // Amount of space on either side of the separator that appears after the icon
42 // or label.
43 constexpr int kIconLabelBubbleSpaceBesideSeparator = 8;
44 
45 // The length of the separator's fade animation. These values are empirical.
46 constexpr int kIconLabelBubbleFadeInDurationMs = 250;
47 constexpr int kIconLabelBubbleFadeOutDurationMs = 175;
48 
49 }  // namespace
50 
GetIconLabelBubbleInkDropColor() const51 SkColor IconLabelBubbleView::Delegate::GetIconLabelBubbleInkDropColor() const {
52   return GetIconLabelBubbleSurroundingForegroundColor();
53 }
54 
SeparatorView(IconLabelBubbleView * owner)55 IconLabelBubbleView::SeparatorView::SeparatorView(IconLabelBubbleView* owner) {
56   DCHECK(owner);
57   owner_ = owner;
58 
59   SetPaintToLayer();
60   layer()->SetFillsBoundsOpaquely(false);
61 }
62 
OnPaint(gfx::Canvas * canvas)63 void IconLabelBubbleView::SeparatorView::OnPaint(gfx::Canvas* canvas) {
64   // This uses the surrounding context as the base color instead of
65   // IconLabelBubbleView::GetForegroundColor() so that if the
66   // IconLabelBubbleView has been emphasized (e.g. red text for a security
67   // error) the separator will still blend into the background.
68   const SkColor separator_color = SkColorSetA(
69       owner_->delegate_->GetIconLabelBubbleSurroundingForegroundColor(), 0x69);
70   const float x = GetLocalBounds().right() -
71                   owner_->GetEndPaddingWithSeparator() -
72                   1.0f / canvas->image_scale();
73   canvas->DrawLine(gfx::PointF(x, GetLocalBounds().y()),
74                    gfx::PointF(x, GetLocalBounds().bottom()), separator_color);
75 }
76 
OnThemeChanged()77 void IconLabelBubbleView::SeparatorView::OnThemeChanged() {
78   views::View::OnThemeChanged();
79   SchedulePaint();
80 }
81 
UpdateOpacity()82 void IconLabelBubbleView::SeparatorView::UpdateOpacity() {
83   if (!GetVisible())
84     return;
85 
86   // When using focus rings are visible we should hide the separator instantly
87   // when the IconLabelBubbleView is focused. Otherwise we should follow the
88   // inkdrop.
89   if (owner_->focus_ring() && owner_->HasFocus()) {
90     layer()->SetOpacity(0.0f);
91     return;
92   }
93 
94   views::InkDrop* ink_drop = owner_->GetInkDrop();
95   DCHECK(ink_drop);
96 
97   // If an inkdrop highlight or ripple is animating in or visible, the
98   // separator should fade out.
99   views::InkDropState state = ink_drop->GetTargetInkDropState();
100   float opacity = 0.0f;
101   float duration = kIconLabelBubbleFadeOutDurationMs;
102   if (!ink_drop->IsHighlightFadingInOrVisible() &&
103       (state == views::InkDropState::HIDDEN ||
104        state == views::InkDropState::ACTION_TRIGGERED ||
105        state == views::InkDropState::DEACTIVATED)) {
106     opacity = 1.0f;
107     duration = kIconLabelBubbleFadeInDurationMs;
108   }
109 
110   ui::ScopedLayerAnimationSettings animation(layer()->GetAnimator());
111   animation.SetTransitionDuration(base::TimeDelta::FromMilliseconds(duration));
112   animation.SetTweenType(gfx::Tween::Type::EASE_IN);
113   layer()->SetOpacity(opacity);
114 }
115 
116 class IconLabelBubbleView::HighlightPathGenerator
117     : public views::HighlightPathGenerator {
118  public:
119   HighlightPathGenerator() = default;
120 
121   // views::HighlightPathGenerator:
GetHighlightPath(const views::View * view)122   SkPath GetHighlightPath(const views::View* view) override {
123     return static_cast<const IconLabelBubbleView*>(view)->GetHighlightPath();
124   }
125 
126  private:
127   DISALLOW_COPY_AND_ASSIGN(HighlightPathGenerator);
128 };
129 
IconLabelBubbleView(const gfx::FontList & font_list,Delegate * delegate)130 IconLabelBubbleView::IconLabelBubbleView(const gfx::FontList& font_list,
131                                          Delegate* delegate)
132     : delegate_(delegate),
133       separator_view_(AddChildView(std::make_unique<SeparatorView>(this))) {
134   DCHECK(delegate_);
135 
136   SetFontList(font_list);
137   SetHorizontalAlignment(gfx::ALIGN_LEFT);
138 
139   separator_view_->SetVisible(ShouldShowSeparator());
140 
141   SetInkDropVisibleOpacity(GetOmniboxStateOpacity(OmniboxPartState::SELECTED));
142   SetInkDropHighlightOpacity(GetOmniboxStateOpacity(OmniboxPartState::HOVERED));
143 
144   views::HighlightPathGenerator::Install(
145       this, std::make_unique<HighlightPathGenerator>());
146   SetFocusBehavior(FocusBehavior::ACCESSIBLE_ONLY);
147 
148   UpdateBorder();
149 
150   SetNotifyEnterExitOnChild(true);
151 
152   // Flip the canvas in RTL so the separator is drawn on the correct side.
153   separator_view_->SetFlipCanvasOnPaintForRTLUI(true);
154 
155   auto alert_view = std::make_unique<views::AXVirtualView>();
156   alert_view->GetCustomData().role = ax::mojom::Role::kAlert;
157   alert_virtual_view_ = alert_view.get();
158   GetViewAccessibility().AddVirtualChildView(std::move(alert_view));
159 }
160 
~IconLabelBubbleView()161 IconLabelBubbleView::~IconLabelBubbleView() {}
162 
InkDropAnimationStarted()163 void IconLabelBubbleView::InkDropAnimationStarted() {
164   separator_view_->UpdateOpacity();
165 }
166 
InkDropRippleAnimationEnded(views::InkDropState state)167 void IconLabelBubbleView::InkDropRippleAnimationEnded(
168     views::InkDropState state) {}
169 
ShouldShowLabel() const170 bool IconLabelBubbleView::ShouldShowLabel() const {
171   if (slide_animation_.is_animating() || is_animation_paused_)
172     return !IsShrinking() || (width() > image()->GetPreferredSize().width());
173   return label()->GetVisible() && !label()->GetText().empty();
174 }
175 
SetLabel(const base::string16 & label_text)176 void IconLabelBubbleView::SetLabel(const base::string16& label_text) {
177   SetAccessibleName(label_text);
178   label()->SetText(label_text);
179   separator_view_->SetVisible(ShouldShowSeparator());
180   separator_view_->UpdateOpacity();
181 }
182 
SetFontList(const gfx::FontList & font_list)183 void IconLabelBubbleView::SetFontList(const gfx::FontList& font_list) {
184   label()->SetFontList(font_list);
185 }
186 
GetForegroundColor() const187 SkColor IconLabelBubbleView::GetForegroundColor() const {
188   return delegate_->GetIconLabelBubbleSurroundingForegroundColor();
189 }
190 
UpdateLabelColors()191 void IconLabelBubbleView::UpdateLabelColors() {
192   SetEnabledTextColors(GetForegroundColor());
193   label()->SetBackgroundColor(delegate_->GetIconLabelBubbleBackgroundColor());
194 }
195 
ShouldShowSeparator() const196 bool IconLabelBubbleView::ShouldShowSeparator() const {
197   return ShouldShowLabel();
198 }
199 
GetWidthBetween(int min,int max) const200 int IconLabelBubbleView::GetWidthBetween(int min, int max) const {
201   // TODO(https://crbug.com/8944): Disable animations globally instead of having
202   // piecemeal opt ins for respecting prefers reduced motion.
203   if (gfx::Animation::PrefersReducedMotion())
204     return max;
205 
206   if (!slide_animation_.is_animating() && !is_animation_paused_)
207     return max;
208 
209   double progress = is_animation_paused_ ? pause_animation_state_
210                                          : slide_animation_.GetCurrentValue();
211   // This tween matches the default for SlideAnimation.
212   const gfx::Tween::Type kTween = gfx::Tween::EASE_OUT;
213   if (progress < open_state_fraction_) {
214     double state =
215         gfx::Tween::CalculateValue(kTween, progress / open_state_fraction_);
216     return gfx::Tween::IntValueBetween(state, min, max);
217   }
218 
219   if (progress <= (1 - open_state_fraction_))
220     return max;
221 
222   double state = gfx::Tween::CalculateValue(
223       kTween, (progress - (1 - open_state_fraction_)) / open_state_fraction_);
224   // Note |min| and |max| are reversed.
225   return gfx::Tween::IntValueBetween(state, max, min);
226 }
227 
IsShrinking() const228 bool IconLabelBubbleView::IsShrinking() const {
229   if (!slide_animation_.is_animating() || is_animation_paused_)
230     return false;
231   return slide_animation_.IsClosing() ||
232          (open_state_fraction_ < 1.0 &&
233           slide_animation_.GetCurrentValue() > (1.0 - open_state_fraction_));
234 }
235 
ShowBubble(const ui::Event & event)236 bool IconLabelBubbleView::ShowBubble(const ui::Event& event) {
237   return false;
238 }
239 
IsBubbleShowing() const240 bool IconLabelBubbleView::IsBubbleShowing() const {
241   return false;
242 }
243 
OnTouchUiChanged()244 void IconLabelBubbleView::OnTouchUiChanged() {
245   UpdateBorder();
246 
247   // PreferredSizeChanged() incurs an expensive layout of the location bar, so
248   // only call it when this view is showing.
249   if (GetVisible())
250     PreferredSizeChanged();
251 }
252 
CalculatePreferredSize() const253 gfx::Size IconLabelBubbleView::CalculatePreferredSize() const {
254   return GetSizeForLabelWidth(label()->GetPreferredSize().width());
255 }
256 
Layout()257 void IconLabelBubbleView::Layout() {
258   ink_drop_container()->SetBoundsRect(GetLocalBounds());
259 
260   // We may not have horizontal room for both the image and the trailing
261   // padding. When the view is expanding (or showing-label steady state), the
262   // image. When the view is contracting (or hidden-label steady state), whittle
263   // away at the trailing padding instead.
264   int bubble_trailing_padding = GetEndPaddingWithSeparator();
265   int image_width = image()->GetPreferredSize().width();
266   const int space_shortage = image_width + bubble_trailing_padding - width();
267   if (space_shortage > 0) {
268     if (ShouldShowLabel())
269       image_width -= space_shortage;
270     else
271       bubble_trailing_padding -= space_shortage;
272   }
273   image()->SetBounds(GetInsets().left(), 0, image_width, height());
274 
275   // Compute the label bounds. The label gets whatever size is left over after
276   // accounting for the preferred image width and padding amounts. Note that if
277   // the label has zero size it doesn't actually matter what we compute its X
278   // value to be, since it won't be visible.
279   const int label_x = image()->bounds().right() + GetInternalSpacing();
280   int label_width = std::max(0, width() - label_x - bubble_trailing_padding -
281                                     GetWidthBetweenIconAndSeparator());
282   label()->SetBounds(label_x, 0, label_width, height());
283 
284   // The separator should be the same height as the icons.
285   const int separator_height = GetLayoutConstant(LOCATION_BAR_ICON_SIZE);
286   gfx::Rect separator_bounds(label()->bounds());
287   separator_bounds.Inset(0, (separator_bounds.height() - separator_height) / 2);
288 
289   float separator_width =
290       GetWidthBetweenIconAndSeparator() + GetEndPaddingWithSeparator();
291   int separator_x = label()->GetText().empty() ? image()->bounds().right()
292                                                : label()->bounds().right();
293   separator_view_->SetBounds(separator_x, separator_bounds.y(), separator_width,
294                              separator_height);
295 
296   if (focus_ring()) {
297     focus_ring()->Layout();
298     focus_ring()->SchedulePaint();
299   }
300 }
301 
OnMousePressed(const ui::MouseEvent & event)302 bool IconLabelBubbleView::OnMousePressed(const ui::MouseEvent& event) {
303   suppress_button_release_ = IsBubbleShowing();
304   return LabelButton::OnMousePressed(event);
305 }
306 
OnThemeChanged()307 void IconLabelBubbleView::OnThemeChanged() {
308   LabelButton::OnThemeChanged();
309 
310   // LabelButton::OnThemeChanged() sets a views::Background on the label
311   // under certain conditions. We don't want that, so unset the background.
312   label()->SetBackground(nullptr);
313 
314   UpdateLabelColors();
315 }
316 
CreateInkDrop()317 std::unique_ptr<views::InkDrop> IconLabelBubbleView::CreateInkDrop() {
318   std::unique_ptr<views::InkDropImpl> ink_drop =
319       CreateDefaultFloodFillInkDropImpl();
320   ink_drop->SetShowHighlightOnFocus(!focus_ring());
321   ink_drop->AddObserver(this);
322   return std::move(ink_drop);
323 }
324 
GetInkDropBaseColor() const325 SkColor IconLabelBubbleView::GetInkDropBaseColor() const {
326   return delegate_->GetIconLabelBubbleInkDropColor();
327 }
328 
IsTriggerableEvent(const ui::Event & event)329 bool IconLabelBubbleView::IsTriggerableEvent(const ui::Event& event) {
330   if (event.IsMouseEvent())
331     return !IsBubbleShowing() && !suppress_button_release_;
332 
333   return true;
334 }
335 
ShouldUpdateInkDropOnClickCanceled() const336 bool IconLabelBubbleView::ShouldUpdateInkDropOnClickCanceled() const {
337   // The click might be cancelled because the bubble is still opened. In this
338   // case, the ink drop state should not be hidden by Button.
339   return false;
340 }
341 
NotifyClick(const ui::Event & event)342 void IconLabelBubbleView::NotifyClick(const ui::Event& event) {
343   LabelButton::NotifyClick(event);
344   ShowBubble(event);
345 }
346 
OnFocus()347 void IconLabelBubbleView::OnFocus() {
348   separator_view_->UpdateOpacity();
349   LabelButton::OnFocus();
350 }
351 
OnBlur()352 void IconLabelBubbleView::OnBlur() {
353   separator_view_->UpdateOpacity();
354   LabelButton::OnBlur();
355 }
356 
AnimationEnded(const gfx::Animation * animation)357 void IconLabelBubbleView::AnimationEnded(const gfx::Animation* animation) {
358   if (animation != &slide_animation_)
359     return views::LabelButton::AnimationEnded(animation);
360 
361   if (!is_animation_paused_) {
362     // If there is no separator to show, then that means we want the text to
363     // disappear after animating.
364     ResetSlideAnimation(/*show_label=*/ShouldShowSeparator());
365     PreferredSizeChanged();
366   }
367 
368   GetInkDrop()->SetShowHighlightOnHover(true);
369   GetInkDrop()->SetShowHighlightOnFocus(!focus_ring());
370 }
371 
AnimationProgressed(const gfx::Animation * animation)372 void IconLabelBubbleView::AnimationProgressed(const gfx::Animation* animation) {
373   if (animation != &slide_animation_)
374     return views::LabelButton::AnimationProgressed(animation);
375 
376   if (!is_animation_paused_)
377     PreferredSizeChanged();
378 }
379 
AnimationCanceled(const gfx::Animation * animation)380 void IconLabelBubbleView::AnimationCanceled(const gfx::Animation* animation) {
381   if (animation != &slide_animation_)
382     return views::LabelButton::AnimationCanceled(animation);
383 
384   AnimationEnded(animation);
385 }
386 
GetAccessibleNodeData(ui::AXNodeData * node_data)387 void IconLabelBubbleView::GetAccessibleNodeData(ui::AXNodeData* node_data) {
388   LabelButton::GetAccessibleNodeData(node_data);
389   if (GetAccessibleName().empty())
390     node_data->SetNameExplicitlyEmpty();
391 }
392 
SetImageModel(const ui::ImageModel & image_model)393 void IconLabelBubbleView::SetImageModel(const ui::ImageModel& image_model) {
394   DCHECK(!image_model.IsEmpty());
395   LabelButton::SetImageModel(STATE_NORMAL, image_model);
396 }
397 
GetSizeForLabelWidth(int label_width) const398 gfx::Size IconLabelBubbleView::GetSizeForLabelWidth(int label_width) const {
399   gfx::Size image_size = image()->GetPreferredSize();
400   image_size.Enlarge(GetInsets().left() + GetWidthBetweenIconAndSeparator() +
401                          GetEndPaddingWithSeparator(),
402                      GetInsets().height());
403 
404   const bool shrinking = IsShrinking();
405   // The out portion of the in-out animation continues for the last few pixels
406   // even after the label is not visible in order to slide the icon into its
407   // final position. Therefore it is necessary to calculate additional width
408   // even when the label is hidden as long as the animation is still shrinking.
409   if (!ShouldShowLabel() && !shrinking)
410     return image_size;
411 
412   const int min_width =
413       shrinking ? image_size.width() : grow_animation_starting_width_;
414   const int max_width = image_size.width() + GetInternalSpacing() + label_width;
415 
416   return gfx::Size(GetWidthBetween(min_width, max_width), image_size.height());
417 }
418 
GetInternalSpacing() const419 int IconLabelBubbleView::GetInternalSpacing() const {
420   if (image()->GetPreferredSize().IsEmpty())
421     return 0;
422   return (ui::TouchUiController::Get()->touch_ui() ? 10 : 8) +
423          GetExtraInternalSpacing();
424 }
425 
GetExtraInternalSpacing() const426 int IconLabelBubbleView::GetExtraInternalSpacing() const {
427   return 0;
428 }
429 
GetWidthBetweenIconAndSeparator() const430 int IconLabelBubbleView::GetWidthBetweenIconAndSeparator() const {
431   return ShouldShowSeparator() ? kIconLabelBubbleSpaceBesideSeparator : 0;
432 }
433 
GetEndPaddingWithSeparator() const434 int IconLabelBubbleView::GetEndPaddingWithSeparator() const {
435   int end_padding = ShouldShowSeparator() ? kIconLabelBubbleSpaceBesideSeparator
436                                           : GetInsets().right();
437   if (ShouldShowSeparator())
438     end_padding += kIconLabelBubbleSeparatorWidth;
439   return end_padding;
440 }
441 
GetClassName() const442 const char* IconLabelBubbleView::GetClassName() const {
443   return "IconLabelBubbleView";
444 }
445 
SetUpForAnimation()446 void IconLabelBubbleView::SetUpForAnimation() {
447   SetInkDropMode(InkDropMode::ON);
448   SetFocusBehavior(FocusBehavior::ACCESSIBLE_ONLY);
449   label()->SetElideBehavior(gfx::NO_ELIDE);
450   label()->SetVisible(false);
451   slide_animation_.SetSlideDuration(base::TimeDelta::FromMilliseconds(150));
452   open_state_fraction_ = 1.0;
453 }
454 
SetUpForInOutAnimation()455 void IconLabelBubbleView::SetUpForInOutAnimation() {
456   SetUpForAnimation();
457   // The duration of the slide includes the appearance of the label (600ms),
458   // statically showing the label (1800ms), and hiding the label (600ms). The
459   // proportion of time spent in each portion of the animation is controlled by
460   // kIconLabelBubbleOpenTimeFraction.
461   slide_animation_.SetSlideDuration(base::TimeDelta::FromMilliseconds(3000));
462   // The tween is calculated in GetWidthBetween().
463   slide_animation_.SetTweenType(gfx::Tween::LINEAR);
464   open_state_fraction_ = 0.2;
465 }
466 
AnimateIn(base::Optional<int> string_id)467 void IconLabelBubbleView::AnimateIn(base::Optional<int> string_id) {
468   if (!label()->GetVisible()) {
469     // Start animation from the current width, otherwise the icon will also be
470     // included if visible.
471     grow_animation_starting_width_ = GetVisible() ? width() : 0;
472     if (string_id) {
473       base::string16 label = l10n_util::GetStringUTF16(string_id.value());
474       SetLabel(label);
475 
476       // Send an accessibility alert whose text is the label's text. Doing this
477       // causes a screenreader to immediately announce the text of the button,
478       // which serves to announce it. This is done unconditionally here if there
479       // is text because the animation is intended to draw attention to the
480       // instance anyway.
481       alert_virtual_view_->GetCustomData().SetName(label);
482       alert_virtual_view_->NotifyAccessibilityEvent(ax::mojom::Event::kAlert);
483     }
484     label()->SetVisible(true);
485     ShowAnimation();
486   }
487 }
488 
AnimateOut()489 void IconLabelBubbleView::AnimateOut() {
490   if (label()->GetVisible()) {
491     label()->SetVisible(false);
492     HideAnimation();
493   }
494 }
495 
ResetSlideAnimation(bool show_label)496 void IconLabelBubbleView::ResetSlideAnimation(bool show_label) {
497   label()->SetVisible(show_label);
498   slide_animation_.Reset(show_label);
499 }
500 
ReduceAnimationTimeForTesting()501 void IconLabelBubbleView::ReduceAnimationTimeForTesting() {
502   slide_animation_.SetSlideDuration(base::TimeDelta::FromMilliseconds(1));
503 }
504 
PauseAnimation()505 void IconLabelBubbleView::PauseAnimation() {
506   if (slide_animation_.is_animating()) {
507     // If the user clicks while we're animating, the bubble arrow will be
508     // pointing to the image, and if we allow the animation to keep running, the
509     // image will move away from the arrow (or we'll have to move the bubble,
510     // which is even worse). So we want to stop the animation.  We have two
511     // choices: jump to the final post-animation state (no label visible), or
512     // pause the animation where we are and continue running after the bubble
513     // closes. The former looks more jerky, so we avoid it unless the animation
514     // hasn't even fully exposed the image yet, in which case pausing with half
515     // an image visible will look broken.
516     if (!is_animation_paused_ && ShouldShowLabel()) {
517       is_animation_paused_ = true;
518       pause_animation_state_ = slide_animation_.GetCurrentValue();
519     }
520     slide_animation_.Reset();
521   }
522 }
523 
UnpauseAnimation()524 void IconLabelBubbleView::UnpauseAnimation() {
525   if (is_animation_paused_) {
526     slide_animation_.Reset(pause_animation_state_);
527     is_animation_paused_ = false;
528     ShowAnimation();
529   }
530 }
531 
GetAnimationValue() const532 double IconLabelBubbleView::GetAnimationValue() const {
533   return slide_animation_.GetCurrentValue();
534 }
535 
ShowAnimation()536 void IconLabelBubbleView::ShowAnimation() {
537   slide_animation_.Show();
538   GetInkDrop()->SetShowHighlightOnHover(false);
539   GetInkDrop()->SetShowHighlightOnFocus(false);
540 }
541 
HideAnimation()542 void IconLabelBubbleView::HideAnimation() {
543   slide_animation_.Hide();
544   GetInkDrop()->SetShowHighlightOnHover(false);
545   GetInkDrop()->SetShowHighlightOnFocus(false);
546 }
547 
GetHighlightPath() const548 SkPath IconLabelBubbleView::GetHighlightPath() const {
549   gfx::Rect highlight_bounds = GetLocalBounds();
550   if (ShouldShowSeparator())
551     highlight_bounds.Inset(0, 0, GetEndPaddingWithSeparator(), 0);
552   highlight_bounds = GetMirroredRect(highlight_bounds);
553 
554   const float corner_radius = highlight_bounds.height() / 2.f;
555   const SkRect rect = RectToSkRect(highlight_bounds);
556 
557   return SkPath().addRoundRect(rect, corner_radius, corner_radius);
558 }
559 
UpdateBorder()560 void IconLabelBubbleView::UpdateBorder() {
561   // Bubbles are given the full internal height of the location bar so that all
562   // child views in the location bar have the same height. The visible height of
563   // the bubble should be smaller, so use an empty border to shrink down the
564   // content bounds so the background gets painted correctly.
565   SetBorder(views::CreateEmptyBorder(
566       gfx::Insets(GetLayoutConstant(LOCATION_BAR_CHILD_INTERIOR_PADDING),
567                   GetLayoutInsets(LOCATION_BAR_ICON_INTERIOR_PADDING).left())));
568 }
569