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