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