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(¶ms); 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