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