extension_installed_bubble_view.cc revision 0529e5d033099cbfc42635f6f6183833b09dff6e
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 : 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 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. 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 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 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. 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 376 virtual gfx::Size GetPreferredSize() 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 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 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 514void ExtensionInstalledBubbleView::Show(const Extension* extension, 515 Browser *browser, 516 const SkBitmap& icon) { 517 new ExtensionInstalledBubbleView(extension, browser, icon); 518} 519 520ExtensionInstalledBubbleView::ExtensionInstalledBubbleView( 521 const Extension* extension, Browser *browser, const SkBitmap& icon) 522 : bubble_(this, extension, browser, icon) { 523} 524 525ExtensionInstalledBubbleView::~ExtensionInstalledBubbleView() {} 526 527bool ExtensionInstalledBubbleView::MaybeShowNow() { 528 BrowserView* browser_view = 529 BrowserView::GetBrowserViewForBrowser(bubble_.browser()); 530 extensions::ExtensionActionManager* extension_action_manager = 531 extensions::ExtensionActionManager::Get(bubble_.browser()->profile()); 532 533 views::View* reference_view = NULL; 534 if (bubble_.type() == bubble_.BROWSER_ACTION) { 535 BrowserActionsContainer* container = 536 browser_view->GetToolbarView()->browser_actions(); 537 if (container->animating()) 538 return false; 539 540 reference_view = container->GetBrowserActionView( 541 extension_action_manager->GetBrowserAction(*bubble_.extension())); 542 // If the view is not visible then it is in the chevron, so point the 543 // install bubble to the chevron instead. If this is an incognito window, 544 // both could be invisible. 545 if (!reference_view || !reference_view->visible()) { 546 reference_view = container->chevron(); 547 if (!reference_view || !reference_view->visible()) 548 reference_view = NULL; // fall back to app menu below. 549 } 550 } else if (bubble_.type() == bubble_.PAGE_ACTION) { 551 LocationBarView* location_bar_view = browser_view->GetLocationBarView(); 552 ExtensionAction* page_action = 553 extension_action_manager->GetPageAction(*bubble_.extension()); 554 location_bar_view->SetPreviewEnabledPageAction(page_action, 555 true); // preview_enabled 556 reference_view = location_bar_view->GetPageActionView(page_action); 557 DCHECK(reference_view); 558 } else if (bubble_.type() == bubble_.OMNIBOX_KEYWORD) { 559 LocationBarView* location_bar_view = browser_view->GetLocationBarView(); 560 reference_view = location_bar_view; 561 DCHECK(reference_view); 562 } 563 564 // Default case. 565 if (reference_view == NULL) 566 reference_view = browser_view->GetToolbarView()->app_menu(); 567 SetAnchorView(reference_view); 568 569 set_arrow(bubble_.type() == bubble_.OMNIBOX_KEYWORD ? 570 views::BubbleBorder::TOP_LEFT : 571 views::BubbleBorder::TOP_RIGHT); 572 SetLayoutManager(new views::FillLayout()); 573 AddChildView(new InstalledBubbleContent( 574 bubble_.browser(), bubble_.extension(), bubble_.type(), 575 &bubble_.icon())); 576 577 views::BubbleDelegateView::CreateBubble(this)->Show(); 578 579 // The bubble widget is now the parent and owner of |this| and takes care of 580 // deletion when the bubble or browser go away. 581 bubble_.IgnoreBrowserClosing(); 582 583 return true; 584} 585 586gfx::Rect ExtensionInstalledBubbleView::GetAnchorRect() { 587 // For omnibox keyword bubbles, move the arrow to point to the left edge 588 // of the omnibox, just to the right of the icon. 589 if (bubble_.type() == bubble_.OMNIBOX_KEYWORD) { 590 LocationBarView* location_bar_view = 591 BrowserView::GetBrowserViewForBrowser(bubble_.browser())-> 592 GetLocationBarView(); 593 return gfx::Rect(location_bar_view->GetOmniboxViewOrigin(), 594 gfx::Size(0, location_bar_view->omnibox_view()->height())); 595 } 596 return views::BubbleDelegateView::GetAnchorRect(); 597} 598 599void ExtensionInstalledBubbleView::WindowClosing() { 600 if (bubble_.extension() && bubble_.type() == bubble_.PAGE_ACTION) { 601 BrowserView* browser_view = 602 BrowserView::GetBrowserViewForBrowser(bubble_.browser()); 603 browser_view->GetLocationBarView()->SetPreviewEnabledPageAction( 604 extensions::ExtensionActionManager::Get(bubble_.browser()->profile())-> 605 GetPageAction(*bubble_.extension()), 606 false); // preview_enabled 607 } 608} 609