1// Copyright 2013 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/extensions/extension_installed_bubble_view.h"
6
7#include <algorithm>
8#include <string>
9
10#include "base/i18n/rtl.h"
11#include "base/strings/utf_string_conversions.h"
12#include "chrome/browser/chrome_notification_types.h"
13#include "chrome/browser/extensions/api/commands/command_service.h"
14#include "chrome/browser/extensions/extension_action.h"
15#include "chrome/browser/extensions/extension_action_manager.h"
16#include "chrome/browser/extensions/extension_install_ui.h"
17#include "chrome/browser/profiles/profile.h"
18#include "chrome/browser/signin/signin_promo.h"
19#include "chrome/browser/ui/browser.h"
20#include "chrome/browser/ui/browser_window.h"
21#include "chrome/browser/ui/singleton_tabs.h"
22#include "chrome/browser/ui/sync/sync_promo_ui.h"
23#include "chrome/browser/ui/views/frame/browser_view.h"
24#include "chrome/browser/ui/views/location_bar/location_bar_view.h"
25#include "chrome/browser/ui/views/location_bar/page_action_with_badge_view.h"
26#include "chrome/browser/ui/views/tabs/tab_strip.h"
27#include "chrome/browser/ui/views/toolbar/browser_action_view.h"
28#include "chrome/browser/ui/views/toolbar/browser_actions_container.h"
29#include "chrome/browser/ui/views/toolbar/toolbar_view.h"
30#include "chrome/common/extensions/api/omnibox/omnibox_handler.h"
31#include "chrome/common/extensions/sync_helper.h"
32#include "chrome/common/url_constants.h"
33#include "chrome/grit/chromium_strings.h"
34#include "chrome/grit/generated_resources.h"
35#include "extensions/common/extension.h"
36#include "ui/base/l10n/l10n_util.h"
37#include "ui/base/resource/resource_bundle.h"
38#include "ui/gfx/render_text.h"
39#include "ui/gfx/text_elider.h"
40#include "ui/resources/grit/ui_resources.h"
41#include "ui/views/controls/button/image_button.h"
42#include "ui/views/controls/image_view.h"
43#include "ui/views/controls/label.h"
44#include "ui/views/controls/link.h"
45#include "ui/views/controls/link_listener.h"
46#include "ui/views/layout/fill_layout.h"
47#include "ui/views/layout/layout_constants.h"
48
49using extensions::Extension;
50
51namespace {
52
53const int kIconSize = 43;
54
55const int kRightColumnWidth = 285;
56
57// The Bubble uses a BubbleBorder which adds about 6 pixels of whitespace
58// around the content view. We compensate by reducing our outer borders by this
59// amount + 4px.
60const int kOuterMarginInset = 10;
61const int kHorizOuterMargin = views::kPanelHorizMargin - kOuterMarginInset;
62const int kVertOuterMargin = views::kPanelVertMargin - kOuterMarginInset;
63
64// Interior vertical margin is 8px smaller than standard
65const int kVertInnerMargin = views::kPanelVertMargin - 8;
66
67// We want to shift the right column (which contains the header and text) up
68// 4px to align with icon.
69const int kRightcolumnVerticalShift = -4;
70
71}  // namespace
72
73namespace chrome {
74
75void ShowExtensionInstalledBubble(const Extension* extension,
76                                  Browser* browser,
77                                  const SkBitmap& icon) {
78  ExtensionInstalledBubbleView::Show(extension, browser, icon);
79}
80
81}  // namespace chrome
82
83// InstalledBubbleContent is the content view which is placed in the
84// ExtensionInstalledBubbleView. It displays the install icon and explanatory
85// text about the installed extension.
86class InstalledBubbleContent : public views::View,
87                               public views::ButtonListener,
88                               public views::LinkListener {
89 public:
90  InstalledBubbleContent(Browser* browser,
91                         const Extension* extension,
92                         ExtensionInstalledBubble::BubbleType type,
93                         const SkBitmap* icon)
94      : browser_(browser),
95        extension_id_(extension->id()),
96        type_(type),
97        flavors_(NONE),
98        height_of_signin_promo_(0u),
99        how_to_use_(NULL),
100        sign_in_link_(NULL),
101        manage_(NULL),
102       manage_shortcut_(NULL) {
103    // The Extension Installed bubble takes on various forms, depending on the
104    // type of extension installed. In general, though, they are all similar:
105    //
106    // -------------------------
107    // |      | Heading    [X] |
108    // | Icon | Info           |
109    // |      | Extra info     |
110    // -------------------------
111    //
112    // Icon and Heading are always shown (as well as the close button).
113    // Info is shown for browser actions, page actions and Omnibox keyword
114    // extensions and might list keyboard shorcut for the former two types.
115    // Extra info is...
116    // ... for other types, either a description of how to manage the extension
117    //     or a link to configure the keybinding shortcut (if one exists).
118    // Extra info can include a promo for signing into sync.
119
120    // First figure out the keybinding situation.
121    extensions::Command command;
122    bool has_keybinding = GetKeybinding(&command);
123    base::string16 key;  // Keyboard shortcut or keyword to display in bubble.
124
125    if (extensions::sync_helper::IsSyncableExtension(extension) &&
126        SyncPromoUI::ShouldShowSyncPromo(browser->profile()))
127      flavors_ |= SIGN_IN_PROMO;
128
129    // Determine the bubble flavor we want, based on the extension type.
130    switch (type_) {
131      case ExtensionInstalledBubble::BROWSER_ACTION:
132      case ExtensionInstalledBubble::PAGE_ACTION: {
133        flavors_ |= HOW_TO_USE;
134        if (has_keybinding) {
135          flavors_ |= SHOW_KEYBINDING;
136          key = command.accelerator().GetShortcutText();
137        } else {
138          // The How-To-Use text makes the bubble seem a little crowded when the
139          // extension has a keybinding, so the How-To-Manage text is not shown
140          // in those cases.
141          flavors_ |= HOW_TO_MANAGE;
142        }
143        break;
144      }
145      case ExtensionInstalledBubble::OMNIBOX_KEYWORD: {
146        flavors_ |= HOW_TO_USE | HOW_TO_MANAGE;
147        key = base::UTF8ToUTF16(extensions::OmniboxInfo::GetKeyword(extension));
148        break;
149      }
150      case ExtensionInstalledBubble::GENERIC: {
151        break;
152      }
153      default: {
154        // When adding a new bubble type, the flavor needs to be set.
155        COMPILE_ASSERT(ExtensionInstalledBubble::GENERIC == 3,
156                       kBubbleTypeEnumHasChangedButNotThisSwitchStatement);
157        break;
158      }
159    }
160
161    ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();
162    const gfx::FontList& font_list =
163        rb.GetFontList(ui::ResourceBundle::BaseFont);
164
165    // Add the icon (for all flavors).
166    // Scale down to 43x43, but allow smaller icons (don't scale up).
167    gfx::Size size(icon->width(), icon->height());
168    if (size.width() > kIconSize || size.height() > kIconSize)
169      size = gfx::Size(kIconSize, kIconSize);
170    icon_ = new views::ImageView();
171    icon_->SetImageSize(size);
172    icon_->SetImage(gfx::ImageSkia::CreateFrom1xBitmap(*icon));
173    AddChildView(icon_);
174
175    // Add the heading (for all flavors).
176    base::string16 extension_name = base::UTF8ToUTF16(extension->name());
177    base::i18n::AdjustStringForLocaleDirection(&extension_name);
178    heading_ = new views::Label(l10n_util::GetStringFUTF16(
179        IDS_EXTENSION_INSTALLED_HEADING, extension_name));
180    heading_->SetFontList(rb.GetFontList(ui::ResourceBundle::MediumFont));
181    heading_->SetMultiLine(true);
182    heading_->SetHorizontalAlignment(gfx::ALIGN_LEFT);
183    AddChildView(heading_);
184
185    if (flavors_ & HOW_TO_USE) {
186      how_to_use_ = new views::Label(GetHowToUseDescription(key));
187      how_to_use_->SetFontList(font_list);
188      how_to_use_->SetMultiLine(true);
189      how_to_use_->SetHorizontalAlignment(gfx::ALIGN_LEFT);
190      AddChildView(how_to_use_);
191    }
192
193    if (flavors_ & SHOW_KEYBINDING) {
194      manage_shortcut_ = new views::Link(
195          l10n_util::GetStringUTF16(IDS_EXTENSION_INSTALLED_MANAGE_SHORTCUTS));
196      manage_shortcut_->set_listener(this);
197      AddChildView(manage_shortcut_);
198    }
199
200    if (flavors_ & HOW_TO_MANAGE) {
201      manage_ = new views::Label(l10n_util::GetStringUTF16(
202#if defined(OS_CHROMEOS)
203          IDS_EXTENSION_INSTALLED_MANAGE_INFO_CHROMEOS));
204#else
205          IDS_EXTENSION_INSTALLED_MANAGE_INFO));
206#endif
207      manage_->SetFontList(font_list);
208      manage_->SetMultiLine(true);
209      manage_->SetHorizontalAlignment(gfx::ALIGN_LEFT);
210      AddChildView(manage_);
211    }
212
213    if (flavors_ & SIGN_IN_PROMO) {
214      signin_promo_text_ =
215          l10n_util::GetStringUTF16(IDS_EXTENSION_INSTALLED_SIGNIN_PROMO);
216
217      signin_promo_link_text_ =
218          l10n_util::GetStringUTF16(IDS_EXTENSION_INSTALLED_SIGNIN_PROMO_LINK);
219      sign_in_link_ = new views::Link(signin_promo_link_text_);
220      sign_in_link_->SetFontList(font_list);
221      sign_in_link_->set_listener(this);
222      AddChildView(sign_in_link_);
223    }
224
225    // Add the Close button (for all flavors).
226    close_button_ = new views::ImageButton(this);
227    close_button_->SetImage(views::CustomButton::STATE_NORMAL,
228        rb.GetImageSkiaNamed(IDR_CLOSE_2));
229    close_button_->SetImage(views::CustomButton::STATE_HOVERED,
230        rb.GetImageSkiaNamed(IDR_CLOSE_2_H));
231    close_button_->SetImage(views::CustomButton::STATE_PRESSED,
232        rb.GetImageSkiaNamed(IDR_CLOSE_2_P));
233    AddChildView(close_button_);
234  }
235
236  virtual void ButtonPressed(views::Button* sender,
237                             const ui::Event& event) OVERRIDE {
238    DCHECK_EQ(sender, close_button_);
239    GetWidget()->Close();
240  }
241
242  // Implements the views::LinkListener interface.
243  virtual void LinkClicked(views::Link* source, int event_flags) OVERRIDE {
244    GetWidget()->Close();
245    std::string configure_url;
246    if (source == manage_shortcut_) {
247      configure_url = chrome::kChromeUIExtensionsURL;
248      configure_url += chrome::kExtensionConfigureCommandsSubPage;
249    } else if (source == sign_in_link_) {
250      configure_url = signin::GetPromoURL(
251          signin::SOURCE_EXTENSION_INSTALL_BUBBLE, false).spec();
252    } else {
253      NOTREACHED();
254      return;
255    }
256    chrome::NavigateParams params(
257        chrome::GetSingletonTabNavigateParams(
258            browser_, GURL(configure_url.c_str())));
259    chrome::Navigate(&params);
260  }
261
262 private:
263  enum Flavors {
264    NONE            = 0,
265    HOW_TO_USE      = 1 << 0,
266    HOW_TO_MANAGE   = 1 << 1,
267    SHOW_KEYBINDING = 1 << 2,
268    SIGN_IN_PROMO   = 1 << 3,
269  };
270
271  bool GetKeybinding(extensions::Command* command) {
272    extensions::CommandService* command_service =
273        extensions::CommandService::Get(browser_->profile());
274    if (type_ == ExtensionInstalledBubble::BROWSER_ACTION) {
275      return command_service->GetBrowserActionCommand(
276          extension_id_,
277          extensions::CommandService::ACTIVE_ONLY,
278          command,
279          NULL);
280    } else if (type_ == ExtensionInstalledBubble::PAGE_ACTION) {
281      return command_service->GetPageActionCommand(
282          extension_id_,
283          extensions::CommandService::ACTIVE_ONLY,
284          command,
285          NULL);
286    } else {
287      return false;
288    }
289  }
290
291  base::string16 GetHowToUseDescription(const base::string16& key) {
292    switch (type_) {
293      case ExtensionInstalledBubble::BROWSER_ACTION:
294        if (!key.empty()) {
295          return l10n_util::GetStringFUTF16(
296              IDS_EXTENSION_INSTALLED_BROWSER_ACTION_INFO_WITH_SHORTCUT, key);
297        } else {
298          return l10n_util::GetStringUTF16(
299              IDS_EXTENSION_INSTALLED_BROWSER_ACTION_INFO);
300        }
301        break;
302      case ExtensionInstalledBubble::PAGE_ACTION:
303        if (!key.empty()) {
304          return l10n_util::GetStringFUTF16(
305              IDS_EXTENSION_INSTALLED_PAGE_ACTION_INFO_WITH_SHORTCUT, key);
306        } else {
307          return l10n_util::GetStringUTF16(
308              IDS_EXTENSION_INSTALLED_PAGE_ACTION_INFO);
309        }
310        break;
311      case ExtensionInstalledBubble::OMNIBOX_KEYWORD:
312        return l10n_util::GetStringFUTF16(
313            IDS_EXTENSION_INSTALLED_OMNIBOX_KEYWORD_INFO, key);
314        break;
315      default:
316        NOTREACHED();
317        break;
318    }
319    return base::string16();
320  }
321
322  // Layout the signin promo at coordinates |offset_x| and |offset_y|. Returns
323  // the height (in pixels) of the promo UI.
324  int LayoutSigninPromo(int offset_x, int offset_y) {
325    sign_in_promo_lines_.clear();
326    int height = 0;
327    gfx::Rect contents_area = GetContentsBounds();
328    if (contents_area.IsEmpty())
329      return height;
330    contents_area.set_width(kRightColumnWidth);
331
332    base::string16 full_text = signin_promo_link_text_ + signin_promo_text_;
333
334    // The link is the first item in the text.
335    const gfx::Size link_size = sign_in_link_->GetPreferredSize();
336    sign_in_link_->SetBounds(
337        offset_x, offset_y, link_size.width(), link_size.height());
338
339    // Word-wrap the full label text.
340    const gfx::FontList font_list;
341    std::vector<base::string16> lines;
342    gfx::ElideRectangleText(full_text, font_list, contents_area.width(),
343                            contents_area.height(), gfx::ELIDE_LONG_WORDS,
344                            &lines);
345
346    gfx::Point position = gfx::Point(
347        contents_area.origin().x() + offset_x,
348        contents_area.origin().y() + offset_y + 1);
349    if (base::i18n::IsRTL()) {
350      position -= gfx::Vector2d(
351          2 * views::kPanelHorizMargin + kHorizOuterMargin, 0);
352    }
353
354    // Loop through the lines, creating a renderer for each.
355    for (std::vector<base::string16>::const_iterator it = lines.begin();
356         it != lines.end(); ++it) {
357      gfx::RenderText* line = gfx::RenderText::CreateInstance();
358      line->SetDirectionalityMode(gfx::DIRECTIONALITY_FROM_UI);
359      line->SetText(*it);
360      const gfx::Size size(contents_area.width(),
361                           line->GetStringSize().height());
362      line->SetDisplayRect(gfx::Rect(position, size));
363      position.set_y(position.y() + size.height());
364      sign_in_promo_lines_.push_back(line);
365      height += size.height();
366    }
367
368    // The link is drawn separately; make it transparent here to only draw once.
369    // The link always leads other text and is assumed to fit on the first line.
370    sign_in_promo_lines_.front()->ApplyColor(SK_ColorTRANSPARENT,
371        gfx::Range(0, signin_promo_link_text_.size()));
372
373    return height;
374  }
375
376  virtual gfx::Size GetPreferredSize() const OVERRIDE {
377    int width = kHorizOuterMargin;
378    width += kIconSize;
379    width += views::kPanelHorizMargin;
380    width += kRightColumnWidth;
381    width += 2 * views::kPanelHorizMargin;
382    width += kHorizOuterMargin;
383
384    int height = kVertOuterMargin;
385    height += heading_->GetHeightForWidth(kRightColumnWidth);
386    height += kVertInnerMargin;
387
388    if (flavors_ & HOW_TO_USE) {
389      height += how_to_use_->GetHeightForWidth(kRightColumnWidth);
390      height += kVertInnerMargin;
391    }
392
393    if (flavors_ & HOW_TO_MANAGE) {
394      height += manage_->GetHeightForWidth(kRightColumnWidth);
395      height += kVertInnerMargin;
396    }
397
398    if (flavors_ & SIGN_IN_PROMO && height_of_signin_promo_ > 0u) {
399      height += height_of_signin_promo_;
400      height += kVertInnerMargin;
401    }
402
403    if (flavors_ & SHOW_KEYBINDING) {
404      height += manage_shortcut_->GetHeightForWidth(kRightColumnWidth);
405      height += kVertInnerMargin;
406    }
407
408    return gfx::Size(width, std::max(height, kIconSize + 2 * kVertOuterMargin));
409  }
410
411  virtual void Layout() OVERRIDE {
412    int x = kHorizOuterMargin;
413    int y = kVertOuterMargin;
414
415    icon_->SetBounds(x, y, kIconSize, kIconSize);
416    x += kIconSize;
417    x += views::kPanelHorizMargin;
418
419    y += kRightcolumnVerticalShift;
420    heading_->SizeToFit(kRightColumnWidth);
421    heading_->SetX(x);
422    heading_->SetY(y);
423    y += heading_->height();
424    y += kVertInnerMargin;
425
426    if (flavors_ & HOW_TO_USE) {
427      how_to_use_->SizeToFit(kRightColumnWidth);
428      how_to_use_->SetX(x);
429      how_to_use_->SetY(y);
430      y += how_to_use_->height();
431      y += kVertInnerMargin;
432    }
433
434    if (flavors_ & HOW_TO_MANAGE) {
435      manage_->SizeToFit(kRightColumnWidth);
436      manage_->SetX(x);
437      manage_->SetY(y);
438      y += manage_->height();
439      y += kVertInnerMargin;
440    }
441
442    if (flavors_ & SIGN_IN_PROMO) {
443      height_of_signin_promo_ = LayoutSigninPromo(x, y);
444      y += height_of_signin_promo_;
445      y += kVertInnerMargin;
446    }
447
448    if (flavors_ & SHOW_KEYBINDING) {
449      gfx::Size sz = manage_shortcut_->GetPreferredSize();
450      manage_shortcut_->SetBounds(width() - 2 * kHorizOuterMargin - sz.width(),
451                                  y,
452                                  sz.width(),
453                                  sz.height());
454      y += manage_shortcut_->height();
455      y += kVertInnerMargin;
456    }
457
458    gfx::Size sz;
459    x += kRightColumnWidth + 2 * views::kPanelHorizMargin + kHorizOuterMargin -
460        close_button_->GetPreferredSize().width();
461    y = kVertOuterMargin;
462    sz = close_button_->GetPreferredSize();
463    // x-1 & y-1 is just slop to get the close button visually aligned with the
464    // title text and bubble arrow.
465    close_button_->SetBounds(x - 1, y - 1, sz.width(), sz.height());
466  }
467
468  virtual void OnPaint(gfx::Canvas* canvas) OVERRIDE {
469    for (ScopedVector<gfx::RenderText>::const_iterator it =
470             sign_in_promo_lines_.begin();
471         it != sign_in_promo_lines_.end(); ++it)
472      (*it)->Draw(canvas);
473
474    views::View::OnPaint(canvas);
475  }
476
477  // The browser we're associated with.
478  Browser* browser_;
479
480  // The id of the extension just installed.
481  const std::string extension_id_;
482
483  // The string that contains the link text at the beginning of the sign-in
484  // promo text.
485  base::string16 signin_promo_link_text_;
486  // The remaining text of the sign-in promo text.
487  base::string16 signin_promo_text_;
488
489  // A vector of RenderText objects representing the full sign-in promo
490  // paragraph as layed out within the bubble, but has the text of the link
491  // whited out so the link can be drawn in its place.
492  ScopedVector<gfx::RenderText> sign_in_promo_lines_;
493
494  // The type of the bubble to show (Browser Action, Omnibox keyword, etc).
495  ExtensionInstalledBubble::BubbleType type_;
496
497  // A bitmask containing the various flavors of bubble sections to show.
498  int flavors_;
499
500  // The height, in pixels, of the sign-in promo.
501  size_t height_of_signin_promo_;
502
503  views::ImageView* icon_;
504  views::Label* heading_;
505  views::Label* how_to_use_;
506  views::Link* sign_in_link_;
507  views::Label* manage_;
508  views::Link* manage_shortcut_;
509  views::ImageButton* close_button_;
510
511  DISALLOW_COPY_AND_ASSIGN(InstalledBubbleContent);
512};
513
514void ExtensionInstalledBubbleView::Show(const Extension* extension,
515                                        Browser* browser,
516                                        const SkBitmap& icon) {
517  new ExtensionInstalledBubbleView(extension, browser, icon);
518}
519
520ExtensionInstalledBubbleView::ExtensionInstalledBubbleView(
521    const Extension* extension, Browser *browser, const SkBitmap& icon)
522    : bubble_(this, extension, browser, icon) {
523}
524
525ExtensionInstalledBubbleView::~ExtensionInstalledBubbleView() {}
526
527bool ExtensionInstalledBubbleView::MaybeShowNow() {
528  BrowserView* browser_view =
529      BrowserView::GetBrowserViewForBrowser(bubble_.browser());
530
531  views::View* reference_view = NULL;
532  if (bubble_.type() == bubble_.BROWSER_ACTION) {
533    BrowserActionsContainer* container =
534        browser_view->GetToolbarView()->browser_actions();
535    if (container->animating())
536      return false;
537
538    reference_view = container->GetViewForExtension(bubble_.extension());
539    // If the view is not visible then it is in the chevron, so point the
540    // install bubble to the chevron instead. If this is an incognito window,
541    // both could be invisible.
542    if (!reference_view || !reference_view->visible()) {
543      reference_view = container->chevron();
544      if (!reference_view || !reference_view->visible())
545        reference_view = NULL;  // fall back to app menu below.
546    }
547  } else if (bubble_.type() == bubble_.PAGE_ACTION) {
548    LocationBarView* location_bar_view = browser_view->GetLocationBarView();
549    ExtensionAction* page_action =
550        extensions::ExtensionActionManager::Get(bubble_.browser()->profile())->
551            GetPageAction(*bubble_.extension());
552    location_bar_view->SetPreviewEnabledPageAction(page_action,
553                                                   true);  // preview_enabled
554    reference_view = location_bar_view->GetPageActionView(page_action);
555    DCHECK(reference_view);
556  } else if (bubble_.type() == bubble_.OMNIBOX_KEYWORD) {
557    LocationBarView* location_bar_view = browser_view->GetLocationBarView();
558    reference_view = location_bar_view;
559    DCHECK(reference_view);
560  }
561
562  // Default case.
563  if (reference_view == NULL)
564    reference_view = browser_view->GetToolbarView()->app_menu();
565  SetAnchorView(reference_view);
566
567  set_arrow(bubble_.type() == bubble_.OMNIBOX_KEYWORD ?
568            views::BubbleBorder::TOP_LEFT :
569            views::BubbleBorder::TOP_RIGHT);
570  SetLayoutManager(new views::FillLayout());
571  AddChildView(new InstalledBubbleContent(
572      bubble_.browser(), bubble_.extension(), bubble_.type(),
573      &bubble_.icon()));
574
575  views::BubbleDelegateView::CreateBubble(this)->Show();
576
577  // The bubble widget is now the parent and owner of |this| and takes care of
578  // deletion when the bubble or browser go away.
579  bubble_.IgnoreBrowserClosing();
580
581  return true;
582}
583
584gfx::Rect ExtensionInstalledBubbleView::GetAnchorRect() const {
585  // For omnibox keyword bubbles, move the arrow to point to the left edge
586  // of the omnibox, just to the right of the icon.
587  if (bubble_.type() == bubble_.OMNIBOX_KEYWORD) {
588    const LocationBarView* location_bar_view =
589        BrowserView::GetBrowserViewForBrowser(bubble_.browser())->
590        GetLocationBarView();
591    return gfx::Rect(location_bar_view->GetOmniboxViewOrigin(),
592        gfx::Size(0, location_bar_view->omnibox_view()->height()));
593  }
594  return views::BubbleDelegateView::GetAnchorRect();
595}
596
597void ExtensionInstalledBubbleView::WindowClosing() {
598  if (bubble_.extension() && bubble_.type() == bubble_.PAGE_ACTION) {
599    BrowserView* browser_view =
600        BrowserView::GetBrowserViewForBrowser(bubble_.browser());
601    browser_view->GetLocationBarView()->SetPreviewEnabledPageAction(
602        extensions::ExtensionActionManager::Get(bubble_.browser()->profile())->
603        GetPageAction(*bubble_.extension()),
604        false);  // preview_enabled
605  }
606}
607