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
49 using extensions::Extension;
50
51 namespace {
52
53 const int kIconSize = 43;
54
55 const 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.
60 const int kOuterMarginInset = 10;
61 const int kHorizOuterMargin = views::kPanelHorizMargin - kOuterMarginInset;
62 const int kVertOuterMargin = views::kPanelVertMargin - kOuterMarginInset;
63
64 // Interior vertical margin is 8px smaller than standard
65 const 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.
69 const int kRightcolumnVerticalShift = -4;
70
71 } // namespace
72
73 namespace chrome {
74
ShowExtensionInstalledBubble(const Extension * extension,Browser * browser,const SkBitmap & icon)75 void 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.
86 class InstalledBubbleContent : public views::View,
87 public views::ButtonListener,
88 public views::LinkListener {
89 public:
InstalledBubbleContent(Browser * browser,const Extension * extension,ExtensionInstalledBubble::BubbleType type,const SkBitmap * icon)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
ButtonPressed(views::Button * sender,const ui::Event & event)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.
LinkClicked(views::Link * source,int event_flags)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(¶ms);
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
GetKeybinding(extensions::Command * command)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
GetHowToUseDescription(const base::string16 & key)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.
LayoutSigninPromo(int offset_x,int offset_y)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
GetPreferredSize() const376 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
Layout()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
OnPaint(gfx::Canvas * canvas)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
Show(const Extension * extension,Browser * browser,const SkBitmap & icon)514 void ExtensionInstalledBubbleView::Show(const Extension* extension,
515 Browser* browser,
516 const SkBitmap& icon) {
517 new ExtensionInstalledBubbleView(extension, browser, icon);
518 }
519
ExtensionInstalledBubbleView(const Extension * extension,Browser * browser,const SkBitmap & icon)520 ExtensionInstalledBubbleView::ExtensionInstalledBubbleView(
521 const Extension* extension, Browser *browser, const SkBitmap& icon)
522 : bubble_(this, extension, browser, icon) {
523 }
524
~ExtensionInstalledBubbleView()525 ExtensionInstalledBubbleView::~ExtensionInstalledBubbleView() {}
526
MaybeShowNow()527 bool 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
GetAnchorRect() const584 gfx::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
WindowClosing()597 void 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