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/zoom_bubble_view.h"
6 
7 #include <cmath>
8 #include <memory>
9 
10 #include "base/auto_reset.h"
11 #include "base/i18n/number_formatting.h"
12 #include "base/i18n/rtl.h"
13 #include "base/strings/stringprintf.h"
14 #include "base/strings/utf_string_conversions.h"
15 #include "build/build_config.h"
16 #include "chrome/app/vector_icons/vector_icons.h"
17 #include "chrome/browser/chrome_notification_types.h"
18 #include "chrome/browser/platform_util.h"
19 #include "chrome/browser/ui/browser.h"
20 #include "chrome/browser/ui/browser_dialogs.h"
21 #include "chrome/browser/ui/browser_finder.h"
22 #include "chrome/browser/ui/browser_tabstrip.h"
23 #include "chrome/browser/ui/browser_window.h"
24 #include "chrome/browser/ui/views/chrome_layout_provider.h"
25 #include "chrome/browser/ui/views/frame/browser_view.h"
26 #include "chrome/browser/ui/views/frame/toolbar_button_provider.h"
27 #include "chrome/browser/ui/views/page_action/zoom_view.h"
28 #include "chrome/grit/generated_resources.h"
29 #include "components/zoom/page_zoom.h"
30 #include "components/zoom/zoom_controller.h"
31 #include "content/public/browser/notification_source.h"
32 #include "extensions/browser/extension_zoom_request_client.h"
33 #include "extensions/common/api/extension_action/action_info.h"
34 #include "extensions/common/manifest_handlers/icons_handler.h"
35 #include "extensions/grit/extensions_browser_resources.h"
36 #include "third_party/blink/public/common/page/page_zoom.h"
37 #include "ui/base/l10n/l10n_util.h"
38 #include "ui/base/resource/resource_bundle.h"
39 #include "ui/gfx/favicon_size.h"
40 #include "ui/gfx/geometry/insets.h"
41 #include "ui/gfx/text_utils.h"
42 #include "ui/gfx/vector_icon_types.h"
43 #include "ui/native_theme/native_theme.h"
44 #include "ui/views/accessibility/ax_virtual_view.h"
45 #include "ui/views/accessibility/view_accessibility.h"
46 #include "ui/views/controls/button/image_button.h"
47 #include "ui/views/controls/button/image_button_factory.h"
48 #include "ui/views/controls/button/md_text_button.h"
49 #include "ui/views/controls/highlight_path_generator.h"
50 #include "ui/views/controls/separator.h"
51 #include "ui/views/layout/box_layout.h"
52 #include "ui/views/layout/fill_layout.h"
53 #include "ui/views/view_class_properties.h"
54 #include "ui/views/widget/widget.h"
55 
56 namespace {
57 
58 // The default time that the bubble should stay on the screen if it will close
59 // automatically.
60 constexpr base::TimeDelta kBubbleCloseDelayDefault =
61     base::TimeDelta::FromMilliseconds(1500);
62 
63 // A longer timeout used for how long the bubble should stay on the screen
64 // before it will close automatically after + or - buttons have been used.
65 constexpr base::TimeDelta kBubbleCloseDelayLong =
66     base::TimeDelta::FromMilliseconds(5000);
67 
68 class ZoomButtonHighlightPathGenerator : public views::HighlightPathGenerator {
69  public:
70   ZoomButtonHighlightPathGenerator() = default;
71 
GetHighlightPath(const views::View * view)72   SkPath GetHighlightPath(const views::View* view) override {
73     constexpr int kCircleRadiusDp = 24 / 2;
74     const gfx::Point center = view->GetLocalBounds().CenterPoint();
75     return SkPath().addCircle(center.x(), center.y(), kCircleRadiusDp);
76   }
77 };
78 
CreateZoomButton(views::Button::PressedCallback callback,const gfx::VectorIcon & icon,int tooltip_id)79 std::unique_ptr<views::ImageButton> CreateZoomButton(
80     views::Button::PressedCallback callback,
81     const gfx::VectorIcon& icon,
82     int tooltip_id) {
83   auto zoom_button =
84       views::CreateVectorImageButtonWithNativeTheme(std::move(callback), icon);
85   zoom_button->SetTooltipText(l10n_util::GetStringUTF16(tooltip_id));
86   views::HighlightPathGenerator::Install(
87       zoom_button.get(), std::make_unique<ZoomButtonHighlightPathGenerator>());
88   return zoom_button;
89 }
90 
91 class ZoomValue : public views::Label {
92  public:
ZoomValue(const content::WebContents * web_contents)93   explicit ZoomValue(const content::WebContents* web_contents)
94       : Label(base::string16(),
95               views::style::CONTEXT_LABEL,
96               views::style::STYLE_PRIMARY),
97         max_width_(GetLabelMaxWidth(web_contents)) {
98     SetHorizontalAlignment(gfx::ALIGN_LEFT);
99   }
~ZoomValue()100   ~ZoomValue() override {}
101 
102   // views::Label:
CalculatePreferredSize() const103   gfx::Size CalculatePreferredSize() const override {
104     return gfx::Size(max_width_, GetHeightForWidth(max_width_));
105   }
106 
107  private:
GetLabelMaxWidth(const content::WebContents * web_contents) const108   int GetLabelMaxWidth(const content::WebContents* web_contents) const {
109     const int border_width = border() ? border()->GetInsets().width() : 0;
110     int max_w = 0;
111     auto* zoom_controller = zoom::ZoomController::FromWebContents(web_contents);
112     DCHECK(zoom_controller);
113     // Enumerate all zoom factors that can be used in PageZoom::Zoom.
114     std::vector<double> zoom_factors =
115         zoom::PageZoom::PresetZoomFactors(zoom_controller->GetZoomPercent());
116     for (auto zoom : zoom_factors) {
117       int w = gfx::GetStringWidth(
118           base::FormatPercent(static_cast<int>(std::round(zoom * 100))),
119           font_list());
120       max_w = std::max(w, max_w);
121     }
122     return max_w + border_width;
123   }
124 
125   const int max_width_;
126 
127   DISALLOW_COPY_AND_ASSIGN(ZoomValue);
128 };
129 
IsBrowserFullscreen(Browser * browser)130 bool IsBrowserFullscreen(Browser* browser) {
131   DCHECK(browser->window() &&
132          browser->exclusive_access_manager()->fullscreen_controller());
133   return browser->window()->IsFullscreen();
134 }
135 
GetAnchorViewForBrowser(Browser * browser)136 views::View* GetAnchorViewForBrowser(Browser* browser) {
137   BrowserView* browser_view = BrowserView::GetBrowserViewForBrowser(browser);
138   if (!IsBrowserFullscreen(browser) || browser_view->IsToolbarVisible() ||
139       browser_view->immersive_mode_controller()->IsRevealed()) {
140     return browser_view->toolbar_button_provider()->GetAnchorView(
141         PageActionIconType::kZoom);
142   }
143   return nullptr;
144 }
145 
GetImmersiveModeControllerForBrowser(Browser * browser)146 ImmersiveModeController* GetImmersiveModeControllerForBrowser(
147     Browser* browser) {
148   BrowserView* browser_view = BrowserView::GetBrowserViewForBrowser(browser);
149   return browser_view->immersive_mode_controller();
150 }
151 
ParentToBrowser(Browser * browser,ZoomBubbleView * zoom_bubble,views::View * anchor_view,content::WebContents * web_contents)152 void ParentToBrowser(Browser* browser,
153                      ZoomBubbleView* zoom_bubble,
154                      views::View* anchor_view,
155                      content::WebContents* web_contents) {
156   BrowserView* const browser_view =
157       BrowserView::GetBrowserViewForBrowser(browser);
158   zoom_bubble->SetHighlightedButton(
159       browser_view->toolbar_button_provider()->GetPageActionIconView(
160           PageActionIconType::kZoom));
161 
162   // If we don't anchor to anything the BrowserView is our parent. This happens
163   // in fullscreen cases.
164   zoom_bubble->set_parent_window(
165       zoom_bubble->anchor_widget()
166           ? nullptr
167           : browser_view->GetWidget()->GetNativeView());
168 
169   views::BubbleDialogDelegateView::CreateBubble(zoom_bubble);
170 }
171 
172 // Find the extension that initiated the zoom change, if any.
GetExtensionZoomRequestClient(const content::WebContents * web_contents)173 const extensions::ExtensionZoomRequestClient* GetExtensionZoomRequestClient(
174     const content::WebContents* web_contents) {
175   const zoom::ZoomController* zoom_controller =
176       zoom::ZoomController::FromWebContents(web_contents);
177   const zoom::ZoomRequestClient* client = zoom_controller->last_client();
178   return static_cast<const extensions::ExtensionZoomRequestClient*>(client);
179 }
180 
181 }  // namespace
182 
183 // static
184 ZoomBubbleView* ZoomBubbleView::zoom_bubble_ = nullptr;
185 
186 // static
ShowBubble(content::WebContents * web_contents,DisplayReason reason)187 void ZoomBubbleView::ShowBubble(content::WebContents* web_contents,
188                                 DisplayReason reason) {
189   Browser* browser = chrome::FindBrowserWithWebContents(web_contents);
190   // |web_contents| could have been unloaded if a tab gets closed and a mouse
191   // event arrives before the zoom icon gets hidden.
192   if (!browser)
193     return;
194 
195   if (RefreshBubbleIfShowing(web_contents))
196     return;
197 
198   // If the bubble is already showing but in a different tab, the current
199   // bubble must be closed and a new one created.
200   CloseCurrentBubble();
201 
202   views::View* anchor_view = GetAnchorViewForBrowser(browser);
203   ImmersiveModeController* immersive_mode_controller =
204       GetImmersiveModeControllerForBrowser(browser);
205 
206   zoom_bubble_ = new ZoomBubbleView(anchor_view, web_contents, reason,
207                                     immersive_mode_controller);
208 
209   const extensions::ExtensionZoomRequestClient* client =
210       GetExtensionZoomRequestClient(web_contents);
211 
212   // If the zoom change was initiated by an extension, capture the relevent
213   // information from it.
214   if (client)
215     zoom_bubble_->SetExtensionInfo(client->extension());
216 
217   ParentToBrowser(browser, zoom_bubble_, anchor_view, web_contents);
218 
219   if (!anchor_view && IsBrowserFullscreen(browser))
220     zoom_bubble_->AdjustForFullscreen(browser->window()->GetBounds());
221 
222   // Do not announce hotkey for refocusing inactive Zoom bubble as it
223   // disappears after a short timeout.
224   zoom_bubble_->ShowForReason(reason, /* allow_refocus_alert */ false);
225   zoom_bubble_->UpdateZoomIconVisibility();
226 }
227 
228 // static
RefreshBubbleIfShowing(const content::WebContents * web_contents)229 bool ZoomBubbleView::RefreshBubbleIfShowing(
230     const content::WebContents* web_contents) {
231   if (!CanRefresh(web_contents))
232     return false;
233 
234   DCHECK_EQ(web_contents, zoom_bubble_->web_contents());
235   zoom_bubble_->Refresh();
236 
237   return true;
238 }
239 
240 // static
CanRefresh(const content::WebContents * web_contents)241 bool ZoomBubbleView::CanRefresh(const content::WebContents* web_contents) {
242   // Can't refresh when there's not already a bubble for this tab.
243   if (!zoom_bubble_ || (zoom_bubble_->web_contents() != web_contents))
244     return false;
245 
246   Browser* browser = chrome::FindBrowserWithWebContents(web_contents);
247   if (!browser ||
248       (zoom_bubble_->GetAnchorView() != GetAnchorViewForBrowser(browser)))
249     return false;
250 
251   const extensions::ExtensionZoomRequestClient* client =
252       GetExtensionZoomRequestClient(web_contents);
253 
254   // Allow refreshes when the client won't create its own bubble; otherwise
255   // the existing bubble would show the wrong zoom value.
256   if (client && client->ShouldSuppressBubble())
257     return true;
258 
259   // Allow refreshes when the existing bubble has the same attribution for
260   // the zoom change, so only the label needs updating.
261   return zoom_bubble_->extension_info_.id ==
262          (client ? client->extension()->id() : std::string());
263 }
264 
265 // static
CloseCurrentBubble()266 void ZoomBubbleView::CloseCurrentBubble() {
267   if (zoom_bubble_)
268     zoom_bubble_->CloseBubble();
269 }
270 
271 // static
GetZoomBubble()272 ZoomBubbleView* ZoomBubbleView::GetZoomBubble() {
273   return zoom_bubble_;
274 }
275 
Refresh()276 void ZoomBubbleView::Refresh() {
277   UpdateZoomPercent();
278   zoom_level_alert_->GetCustomData().SetName(GetAccessibleWindowTitle());
279   zoom_level_alert_->NotifyAccessibilityEvent(ax::mojom::Event::kAlert);
280   StartTimerIfNecessary();
281 }
282 
ZoomBubbleView(views::View * anchor_view,content::WebContents * web_contents,DisplayReason reason,ImmersiveModeController * immersive_mode_controller)283 ZoomBubbleView::ZoomBubbleView(
284     views::View* anchor_view,
285     content::WebContents* web_contents,
286     DisplayReason reason,
287     ImmersiveModeController* immersive_mode_controller)
288     : LocationBarBubbleDelegateView(anchor_view, web_contents),
289       auto_close_duration_(kBubbleCloseDelayDefault),
290       auto_close_(reason == AUTOMATIC),
291       immersive_mode_controller_(immersive_mode_controller),
292       session_id_(
293           chrome::FindBrowserWithWebContents(web_contents)->session_id()) {
294   SetButtons(ui::DIALOG_BUTTON_NONE);
295 
296   SetNotifyEnterExitOnChild(true);
297   if (immersive_mode_controller_)
298     immersive_mode_controller_->AddObserver(this);
299   UseCompactMargins();
300   chrome::RecordDialogCreation(chrome::DialogIdentifier::ZOOM);
301 }
302 
~ZoomBubbleView()303 ZoomBubbleView::~ZoomBubbleView() {
304   if (immersive_mode_controller_)
305     immersive_mode_controller_->RemoveObserver(this);
306 }
307 
GetAccessibleWindowTitle() const308 base::string16 ZoomBubbleView::GetAccessibleWindowTitle() const {
309   Browser* browser = chrome::FindBrowserWithWebContents(web_contents());
310   if (!browser)
311     return {};
312   return BrowserView::GetBrowserViewForBrowser(browser)
313       ->toolbar_button_provider()
314       ->GetPageActionIconView(PageActionIconType::kZoom)
315       ->GetTextForTooltipAndAccessibleName();
316 }
317 
OnFocus()318 void ZoomBubbleView::OnFocus() {
319   LocationBarBubbleDelegateView::OnFocus();
320   StopTimer();
321 }
322 
OnBlur()323 void ZoomBubbleView::OnBlur() {
324   LocationBarBubbleDelegateView::OnBlur();
325 
326   const views::FocusManager* focus_manager = GetFocusManager();
327   if (focus_manager && Contains(focus_manager->GetFocusedView()))
328     return;
329 
330   StartTimerIfNecessary();
331 }
332 
OnGestureEvent(ui::GestureEvent * event)333 void ZoomBubbleView::OnGestureEvent(ui::GestureEvent* event) {
334   if (!zoom_bubble_ || !zoom_bubble_->auto_close_ ||
335       event->type() != ui::ET_GESTURE_TAP) {
336     return;
337   }
338 
339   auto_close_ = false;
340   StopTimer();
341   event->SetHandled();
342 }
343 
OnKeyEvent(ui::KeyEvent * event)344 void ZoomBubbleView::OnKeyEvent(ui::KeyEvent* event) {
345   if (!zoom_bubble_ || !zoom_bubble_->auto_close_)
346     return;
347 
348   const views::FocusManager* focus_manager = GetFocusManager();
349   if (focus_manager && Contains(focus_manager->GetFocusedView()))
350     StopTimer();
351   else
352     StartTimerIfNecessary();
353 }
354 
OnMouseEntered(const ui::MouseEvent & event)355 void ZoomBubbleView::OnMouseEntered(const ui::MouseEvent& event) {
356   StopTimer();
357 }
358 
OnMouseExited(const ui::MouseEvent & event)359 void ZoomBubbleView::OnMouseExited(const ui::MouseEvent& event) {
360   StartTimerIfNecessary();
361 }
362 
Init()363 void ZoomBubbleView::Init() {
364   // Set up the layout of the zoom bubble.
365   constexpr int kPercentLabelPadding = 64;
366   const ChromeLayoutProvider* provider = ChromeLayoutProvider::Get();
367   const int spacing =
368       provider->GetDistanceMetric(DISTANCE_UNRELATED_CONTROL_HORIZONTAL);
369   auto box_layout = std::make_unique<views::BoxLayout>(
370       views::BoxLayout::Orientation::kHorizontal,
371       provider->GetInsetsMetric(INSETS_TOAST) - margins(), spacing);
372   box_layout->set_main_axis_alignment(
373       views::BoxLayout::MainAxisAlignment::kCenter);
374   box_layout->set_cross_axis_alignment(
375       views::BoxLayout::CrossAxisAlignment::kCenter);
376   SetLayoutManager(std::move(box_layout));
377 
378   // Calculate child views margins in |this| client view.
379   const int label_vertical_spacing =
380       provider->GetDistanceMetric(DISTANCE_TOAST_LABEL_VERTICAL);
381   const gfx::Insets label_margin(label_vertical_spacing - margins().top(), 0,
382                                  label_vertical_spacing - margins().bottom(),
383                                  kPercentLabelPadding - spacing);
384 
385   // Account for the apparent margins that vector buttons have around icons.
386   const int control_vertical_spacing =
387       provider->GetDistanceMetric(DISTANCE_TOAST_CONTROL_VERTICAL);
388   const gfx::Insets control_vertical_margin(
389       control_vertical_spacing - margins().top(), 0,
390       control_vertical_spacing - margins().bottom(), 0);
391   const gfx::Insets vector_button_margin(
392       control_vertical_margin -
393       provider->GetInsetsMetric(views::INSETS_VECTOR_IMAGE_BUTTON));
394 
395   const auto button_pressed_callback = [this](base::RepeatingClosure closure) {
396     return base::BindRepeating(&ZoomBubbleView::ButtonPressed,
397                                base::Unretained(this), std::move(closure));
398   };
399 
400   // If this zoom change was initiated by an extension, that extension will be
401   // attributed by showing its icon in the zoom bubble.
402   if (extension_info_.icon_image) {
403     auto image_button = std::make_unique<views::ImageButton>(
404         button_pressed_callback(base::BindRepeating(
405             &ZoomBubbleView::ImageButtonPressed, base::Unretained(this))));
406     image_button->SetTooltipText(
407         l10n_util::GetStringFUTF16(IDS_TOOLTIP_ZOOM_EXTENSION_ICON,
408                                    base::UTF8ToUTF16(extension_info_.name)));
409     image_button->SetImage(views::Button::STATE_NORMAL,
410                            &extension_info_.icon_image->image_skia());
411     image_button_ = AddChildView(std::move(image_button));
412   }
413 
414   // Add zoom label with the new zoom percent.
415   auto label = std::make_unique<ZoomValue>(web_contents());
416   label->SetProperty(views::kMarginsKey, gfx::Insets(label_margin));
417   label_ = label.get();
418   AddChildView(std::move(label));
419 
420   const auto zoom_callback = [button_pressed_callback,
421                               web_contents =
422                                   web_contents()](content::PageZoom zoom) {
423     return button_pressed_callback(base::BindRepeating(
424         &zoom::PageZoom::Zoom, base::Unretained(web_contents), zoom));
425   };
426 
427   // Add Zoom Out ("-") button.
428   zoom_out_button_ =
429       AddChildView(CreateZoomButton(zoom_callback(content::PAGE_ZOOM_OUT),
430                                     kRemoveIcon, IDS_ACCNAME_ZOOM_MINUS2));
431   zoom_out_button_->SetProperty(views::kMarginsKey,
432                                 gfx::Insets(vector_button_margin));
433 
434   // Add Zoom In ("+") button.
435   zoom_in_button_ = AddChildView(CreateZoomButton(
436       zoom_callback(content::PAGE_ZOOM_IN), kAddIcon, IDS_ACCNAME_ZOOM_PLUS2));
437   zoom_in_button_->SetProperty(views::kMarginsKey,
438                                gfx::Insets(vector_button_margin));
439 
440   // Add "Reset" button.
441   auto reset_button = std::make_unique<views::MdTextButton>(
442       zoom_callback(content::PAGE_ZOOM_RESET),
443       l10n_util::GetStringUTF16(IDS_ZOOM_SET_DEFAULT));
444   reset_button->SetTooltipText(
445       l10n_util::GetStringUTF16(IDS_ACCNAME_ZOOM_SET_DEFAULT));
446   reset_button_ = AddChildView(std::move(reset_button));
447 
448   auto zoom_level_alert = std::make_unique<views::AXVirtualView>();
449   zoom_level_alert->GetCustomData().role = ax::mojom::Role::kAlert;
450   zoom_level_alert_ = zoom_level_alert.get();
451   GetViewAccessibility().AddVirtualChildView(std::move(zoom_level_alert));
452 
453   UpdateZoomPercent();
454   StartTimerIfNecessary();
455 }
456 
WindowClosing()457 void ZoomBubbleView::WindowClosing() {
458   // |zoom_bubble_| can be a new bubble by this point (as Close(); doesn't
459   // call this right away). Only set to nullptr when it's this bubble.
460   bool this_bubble = zoom_bubble_ == this;
461   if (this_bubble)
462     zoom_bubble_ = nullptr;
463 
464   UpdateZoomIconVisibility();
465 }
466 
CloseBubble()467 void ZoomBubbleView::CloseBubble() {
468   Browser* browser = chrome::FindBrowserWithWebContents(web_contents());
469   if (ignore_close_bubble_ &&
470       GetAnchorViewForBrowser(browser) == GetAnchorView()) {
471     return;
472   }
473 
474   // Widget's Close() is async, but we don't want to use zoom_bubble_ after
475   // this. Additionally web_contents() may have been destroyed.
476   zoom_bubble_ = nullptr;
477   LocationBarBubbleDelegateView::CloseBubble();
478 }
479 
OnImmersiveRevealStarted()480 void ZoomBubbleView::OnImmersiveRevealStarted() {
481   CloseBubble();
482 }
483 
OnImmersiveModeControllerDestroyed()484 void ZoomBubbleView::OnImmersiveModeControllerDestroyed() {
485   immersive_mode_controller_ = nullptr;
486 }
487 
OnExtensionIconImageChanged(extensions::IconImage *)488 void ZoomBubbleView::OnExtensionIconImageChanged(
489     extensions::IconImage* /* image */) {
490   image_button_->SetImage(views::Button::STATE_NORMAL,
491                           &extension_info_.icon_image->image_skia());
492   image_button_->SchedulePaint();
493 }
494 
SetExtensionInfo(const extensions::Extension * extension)495 void ZoomBubbleView::SetExtensionInfo(const extensions::Extension* extension) {
496   DCHECK(extension);
497   extension_info_.id = extension->id();
498   extension_info_.name = extension->name();
499 
500   ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();
501   const gfx::ImageSkia& default_extension_icon_image =
502       *rb.GetImageSkiaNamed(IDR_EXTENSIONS_FAVICON);
503   int icon_size = gfx::kFaviconSize;
504 
505   // We give first preference to an icon from the extension's icon set that
506   // matches the size of the default. But not all extensions will declare an
507   // icon set, or may not have an icon of the default size (we don't want the
508   // bubble to display, for example, a very large icon). In that case, if there
509   // is an action icon (size-16) this is an acceptable alternative.
510   const ExtensionIconSet* icons = &extensions::IconsInfo::GetIcons(extension);
511   bool has_default_sized_icon =
512       !icons->Get(gfx::kFaviconSize, ExtensionIconSet::MATCH_EXACTLY).empty();
513 
514   if (!has_default_sized_icon) {
515     const extensions::ActionInfo* action =
516         extensions::ActionInfo::GetExtensionActionInfo(extension);
517     if (!action || action->default_icon.empty())
518       return;  // Out of options.
519 
520     icons = &action->default_icon;
521     icon_size = icons->map().begin()->first;
522   }
523 
524   extension_info_.icon_image = std::make_unique<extensions::IconImage>(
525       web_contents()->GetBrowserContext(), extension, *icons, icon_size,
526       default_extension_icon_image, this);
527 }
528 
UpdateZoomPercent()529 void ZoomBubbleView::UpdateZoomPercent() {
530   label_->SetText(base::FormatPercent(
531       zoom::ZoomController::FromWebContents(web_contents())->GetZoomPercent()));
532 
533   // Disable buttons at min, max and default
534   auto* zoom_controller = zoom::ZoomController::FromWebContents(web_contents());
535   double current_zoom_level = zoom_controller->GetZoomLevel();
536   double default_zoom_level = zoom_controller->GetDefaultZoomLevel();
537   std::vector<double> zoom_levels =
538       zoom::PageZoom::PresetZoomLevels(default_zoom_level);
539   DCHECK(zoom_out_button_);
540   zoom_out_button_->SetEnabled(
541       !blink::PageZoomValuesEqual(zoom_levels.front(), current_zoom_level));
542   DCHECK(zoom_in_button_);
543   zoom_in_button_->SetEnabled(
544       !blink::PageZoomValuesEqual(zoom_levels.back(), current_zoom_level));
545 }
546 
UpdateZoomIconVisibility()547 void ZoomBubbleView::UpdateZoomIconVisibility() {
548   // Note that we can't rely on web_contents() here, as it may have been
549   // destroyed by the time we get this call. Also note parent_window() (if set)
550   // may also be destroyed: the call to WindowClosing() may be triggered by
551   // parent window destruction tearing down its child windows.
552   Browser* browser = chrome::FindBrowserWithID(session_id_);
553   if (browser && browser->window())
554     browser->window()->UpdatePageActionIcon(PageActionIconType::kZoom);
555 }
556 
StartTimerIfNecessary()557 void ZoomBubbleView::StartTimerIfNecessary() {
558   if (!auto_close_)
559     return;
560 
561   auto_close_timer_.Start(FROM_HERE, auto_close_duration_, this,
562                           &ZoomBubbleView::CloseBubble);
563 }
564 
StopTimer()565 void ZoomBubbleView::StopTimer() {
566   auto_close_timer_.Stop();
567 }
568 
ButtonPressed(base::RepeatingClosure closure)569 void ZoomBubbleView::ButtonPressed(base::RepeatingClosure closure) {
570   // No button presses in this dialog should cause the dialog to close,
571   // including when the zoom level is set to 100% as a result of a button press.
572   base::AutoReset<bool> auto_ignore_close_bubble(&ignore_close_bubble_, true);
573 
574   // Extend the timer to give a user more time after any button is pressed.
575   auto_close_duration_ = kBubbleCloseDelayLong;
576   StartTimerIfNecessary();
577 
578   closure.Run();
579 }
580 
ImageButtonPressed()581 void ZoomBubbleView::ImageButtonPressed() {
582   DCHECK(extension_info_.icon_image) << "Invalid button press.";
583   Browser* browser = chrome::FindBrowserWithWebContents(web_contents());
584   DCHECK(browser);
585   chrome::AddSelectedTabWithURL(
586       browser,
587       GURL(base::StringPrintf("chrome://extensions?id=%s",
588                               extension_info_.id.c_str())),
589       ui::PAGE_TRANSITION_FROM_API);
590 }
591 
ZoomBubbleExtensionInfo()592 ZoomBubbleView::ZoomBubbleExtensionInfo::ZoomBubbleExtensionInfo() {}
593 
~ZoomBubbleExtensionInfo()594 ZoomBubbleView::ZoomBubbleExtensionInfo::~ZoomBubbleExtensionInfo() {}
595