| // Copyright (c) 2012 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "chrome/browser/ui/views/extensions/extension_installed_bubble.h" |
| |
| #include <algorithm> |
| #include <string> |
| |
| #include "base/bind.h" |
| #include "base/i18n/rtl.h" |
| #include "base/message_loop/message_loop.h" |
| #include "base/strings/utf_string_conversions.h" |
| #include "chrome/browser/chrome_notification_types.h" |
| #include "chrome/browser/extensions/api/commands/command_service.h" |
| #include "chrome/browser/extensions/extension_action.h" |
| #include "chrome/browser/extensions/extension_action_manager.h" |
| #include "chrome/browser/extensions/extension_install_ui.h" |
| #include "chrome/browser/profiles/profile.h" |
| #include "chrome/browser/signin/signin_promo.h" |
| #include "chrome/browser/ui/browser.h" |
| #include "chrome/browser/ui/browser_window.h" |
| #include "chrome/browser/ui/singleton_tabs.h" |
| #include "chrome/browser/ui/sync/sync_promo_ui.h" |
| #include "chrome/browser/ui/views/browser_action_view.h" |
| #include "chrome/browser/ui/views/browser_actions_container.h" |
| #include "chrome/browser/ui/views/frame/browser_view.h" |
| #include "chrome/browser/ui/views/location_bar/location_bar_view.h" |
| #include "chrome/browser/ui/views/tabs/tab_strip.h" |
| #include "chrome/browser/ui/views/toolbar_view.h" |
| #include "chrome/common/extensions/api/extension_action/action_info.h" |
| #include "chrome/common/extensions/api/omnibox/omnibox_handler.h" |
| #include "chrome/common/extensions/extension.h" |
| #include "chrome/common/extensions/sync_helper.h" |
| #include "chrome/common/url_constants.h" |
| #include "content/public/browser/notification_details.h" |
| #include "content/public/browser/notification_source.h" |
| #include "grit/chromium_strings.h" |
| #include "grit/generated_resources.h" |
| #include "grit/ui_resources.h" |
| #include "ui/base/l10n/l10n_util.h" |
| #include "ui/base/resource/resource_bundle.h" |
| #include "ui/base/text/text_elider.h" |
| #include "ui/gfx/render_text.h" |
| #include "ui/views/controls/button/image_button.h" |
| #include "ui/views/controls/image_view.h" |
| #include "ui/views/controls/label.h" |
| #include "ui/views/controls/link.h" |
| #include "ui/views/controls/link_listener.h" |
| #include "ui/views/layout/fill_layout.h" |
| #include "ui/views/layout/layout_constants.h" |
| |
| using extensions::Extension; |
| |
| namespace { |
| |
| const int kIconSize = 43; |
| |
| const int kRightColumnWidth = 285; |
| |
| // The Bubble uses a BubbleBorder which adds about 6 pixels of whitespace |
| // around the content view. We compensate by reducing our outer borders by this |
| // amount + 4px. |
| const int kOuterMarginInset = 10; |
| const int kHorizOuterMargin = views::kPanelHorizMargin - kOuterMarginInset; |
| const int kVertOuterMargin = views::kPanelVertMargin - kOuterMarginInset; |
| |
| // Interior vertical margin is 8px smaller than standard |
| const int kVertInnerMargin = views::kPanelVertMargin - 8; |
| |
| // We want to shift the right column (which contains the header and text) up |
| // 4px to align with icon. |
| const int kRightcolumnVerticalShift = -4; |
| |
| // How long to wait for browser action animations to complete before retrying. |
| const int kAnimationWaitTime = 50; |
| |
| // How often we retry when waiting for browser action animation to end. |
| const int kAnimationWaitMaxRetry = 10; |
| |
| } // namespace |
| |
| namespace chrome { |
| |
| void ShowExtensionInstalledBubble(const Extension* extension, |
| Browser* browser, |
| const SkBitmap& icon) { |
| ExtensionInstalledBubble::Show(extension, browser, icon); |
| } |
| |
| } // namespace chrome |
| |
| // InstalledBubbleContent is the content view which is placed in the |
| // ExtensionInstalledBubble. It displays the install icon and explanatory |
| // text about the installed extension. |
| class InstalledBubbleContent : public views::View, |
| public views::ButtonListener, |
| public views::LinkListener { |
| public: |
| InstalledBubbleContent(Browser* browser, |
| const Extension* extension, |
| ExtensionInstalledBubble::BubbleType type, |
| SkBitmap* icon, |
| ExtensionInstalledBubble* bubble) |
| : browser_(browser), |
| extension_id_(extension->id()), |
| bubble_(bubble), |
| type_(type), |
| flavors_(NONE), |
| height_of_signin_promo_(0u), |
| how_to_use_(NULL), |
| sign_in_link_(NULL), |
| manage_(NULL), |
| manage_shortcut_(NULL) { |
| // The Extension Installed bubble takes on various forms, depending on the |
| // type of extension installed. In general, though, they are all similar: |
| // |
| // ------------------------- |
| // | | Heading [X] | |
| // | Icon | Info | |
| // | | Extra info | |
| // ------------------------- |
| // |
| // Icon and Heading are always shown (as well as the close button). |
| // Info is shown for browser actions, page actions and Omnibox keyword |
| // extensions and might list keyboard shorcut for the former two types. |
| // Extra info is... |
| // ... for other types, either a description of how to manage the extension |
| // or a link to configure the keybinding shortcut (if one exists). |
| // Extra info can include a promo for signing into sync. |
| |
| // First figure out the keybinding situation. |
| extensions::Command command; |
| bool has_keybinding = GetKeybinding(&command); |
| string16 key; // Keyboard shortcut or keyword to display in the bubble. |
| |
| if (extensions::sync_helper::IsSyncableExtension(extension) && |
| SyncPromoUI::ShouldShowSyncPromo(browser->profile())) |
| flavors_ |= SIGN_IN_PROMO; |
| |
| // Determine the bubble flavor we want, based on the extension type. |
| switch (type_) { |
| case ExtensionInstalledBubble::BROWSER_ACTION: |
| case ExtensionInstalledBubble::PAGE_ACTION: { |
| flavors_ |= HOW_TO_USE; |
| if (has_keybinding) { |
| flavors_ |= SHOW_KEYBINDING; |
| key = command.accelerator().GetShortcutText(); |
| } else { |
| // The How-To-Use text makes the bubble seem a little crowded when the |
| // extension has a keybinding, so the How-To-Manage text is not shown |
| // in those cases. |
| flavors_ |= HOW_TO_MANAGE; |
| } |
| break; |
| } |
| case ExtensionInstalledBubble::OMNIBOX_KEYWORD: { |
| flavors_ |= HOW_TO_USE | HOW_TO_MANAGE; |
| key = UTF8ToUTF16(extensions::OmniboxInfo::GetKeyword(extension)); |
| break; |
| } |
| case ExtensionInstalledBubble::GENERIC: { |
| break; |
| } |
| default: { |
| // When adding a new bubble type, the flavor needs to be set. |
| COMPILE_ASSERT(ExtensionInstalledBubble::GENERIC == 3, |
| kBubbleTypeEnumHasChangedButNotThisSwitchStatement); |
| break; |
| } |
| } |
| |
| ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance(); |
| const gfx::Font& font = rb.GetFont(ui::ResourceBundle::BaseFont); |
| |
| // Add the icon (for all flavors). |
| // Scale down to 43x43, but allow smaller icons (don't scale up). |
| gfx::Size size(icon->width(), icon->height()); |
| if (size.width() > kIconSize || size.height() > kIconSize) |
| size = gfx::Size(kIconSize, kIconSize); |
| icon_ = new views::ImageView(); |
| icon_->SetImageSize(size); |
| icon_->SetImage(gfx::ImageSkia::CreateFrom1xBitmap(*icon)); |
| AddChildView(icon_); |
| |
| // Add the heading (for all flavors). |
| string16 extension_name = UTF8ToUTF16(extension->name()); |
| base::i18n::AdjustStringForLocaleDirection(&extension_name); |
| heading_ = new views::Label(l10n_util::GetStringFUTF16( |
| IDS_EXTENSION_INSTALLED_HEADING, extension_name)); |
| heading_->SetFont(rb.GetFont(ui::ResourceBundle::MediumFont)); |
| heading_->SetMultiLine(true); |
| heading_->SetHorizontalAlignment(gfx::ALIGN_LEFT); |
| AddChildView(heading_); |
| |
| if (flavors_ & HOW_TO_USE) { |
| how_to_use_ = new views::Label(GetHowToUseDescription(key)); |
| how_to_use_->SetFont(font); |
| how_to_use_->SetMultiLine(true); |
| how_to_use_->SetHorizontalAlignment(gfx::ALIGN_LEFT); |
| AddChildView(how_to_use_); |
| } |
| |
| if (flavors_ & SHOW_KEYBINDING) { |
| manage_shortcut_ = new views::Link( |
| l10n_util::GetStringUTF16(IDS_EXTENSION_INSTALLED_MANAGE_SHORTCUTS)); |
| manage_shortcut_->set_listener(this); |
| AddChildView(manage_shortcut_); |
| } |
| |
| if (flavors_ & HOW_TO_MANAGE) { |
| manage_ = new views::Label(l10n_util::GetStringUTF16( |
| #if defined(OS_CHROMEOS) |
| IDS_EXTENSION_INSTALLED_MANAGE_INFO_CHROMEOS)); |
| #else |
| IDS_EXTENSION_INSTALLED_MANAGE_INFO)); |
| #endif |
| manage_->SetFont(font); |
| manage_->SetMultiLine(true); |
| manage_->SetHorizontalAlignment(gfx::ALIGN_LEFT); |
| AddChildView(manage_); |
| } |
| |
| if (flavors_ & SIGN_IN_PROMO) { |
| signin_promo_text_ = |
| l10n_util::GetStringUTF16(IDS_EXTENSION_INSTALLED_SIGNIN_PROMO); |
| |
| signin_promo_link_text_ = |
| l10n_util::GetStringFUTF16( |
| IDS_EXTENSION_INSTALLED_SIGNIN_PROMO_LINK, |
| l10n_util::GetStringUTF16(IDS_SHORT_PRODUCT_NAME)); |
| sign_in_link_ = new views::Link(signin_promo_link_text_); |
| sign_in_link_->SetFont(font); |
| sign_in_link_->set_listener(this); |
| AddChildView(sign_in_link_); |
| } |
| |
| // Add the Close button (for all flavors). |
| close_button_ = new views::ImageButton(this); |
| close_button_->SetImage(views::CustomButton::STATE_NORMAL, |
| rb.GetImageSkiaNamed(IDR_CLOSE_2)); |
| close_button_->SetImage(views::CustomButton::STATE_HOVERED, |
| rb.GetImageSkiaNamed(IDR_CLOSE_2_H)); |
| close_button_->SetImage(views::CustomButton::STATE_PRESSED, |
| rb.GetImageSkiaNamed(IDR_CLOSE_2_P)); |
| AddChildView(close_button_); |
| } |
| |
| virtual void ButtonPressed(views::Button* sender, |
| const ui::Event& event) OVERRIDE { |
| if (sender == close_button_) |
| bubble_->StartFade(false); |
| else |
| NOTREACHED() << "Unknown view"; |
| } |
| |
| // Implements the views::LinkListener interface. |
| virtual void LinkClicked(views::Link* source, int event_flags) OVERRIDE { |
| GetWidget()->Close(); |
| std::string configure_url; |
| if (source == manage_shortcut_) { |
| configure_url = chrome::kChromeUIExtensionsURL; |
| configure_url += chrome::kExtensionConfigureCommandsSubPage; |
| } else if (source == sign_in_link_) { |
| configure_url = signin::GetPromoURL( |
| signin::SOURCE_EXTENSION_INSTALL_BUBBLE, false).spec(); |
| } else { |
| NOTREACHED(); |
| return; |
| } |
| chrome::NavigateParams params( |
| chrome::GetSingletonTabNavigateParams( |
| browser_, GURL(configure_url.c_str()))); |
| chrome::Navigate(¶ms); |
| } |
| |
| private: |
| enum Flavors { |
| NONE = 0, |
| HOW_TO_USE = 1 << 0, |
| HOW_TO_MANAGE = 1 << 1, |
| SHOW_KEYBINDING = 1 << 2, |
| SIGN_IN_PROMO = 1 << 3, |
| }; |
| |
| bool GetKeybinding(extensions::Command* command) { |
| extensions::CommandService* command_service = |
| extensions::CommandService::Get(browser_->profile()); |
| if (type_ == ExtensionInstalledBubble::BROWSER_ACTION) { |
| return command_service->GetBrowserActionCommand( |
| extension_id_, |
| extensions::CommandService::ACTIVE_ONLY, |
| command, |
| NULL); |
| } else if (type_ == ExtensionInstalledBubble::PAGE_ACTION) { |
| return command_service->GetPageActionCommand( |
| extension_id_, |
| extensions::CommandService::ACTIVE_ONLY, |
| command, |
| NULL); |
| } else { |
| return false; |
| } |
| } |
| |
| string16 GetHowToUseDescription(const string16& key) { |
| switch (type_) { |
| case ExtensionInstalledBubble::BROWSER_ACTION: |
| if (!key.empty()) { |
| return l10n_util::GetStringFUTF16( |
| IDS_EXTENSION_INSTALLED_BROWSER_ACTION_INFO_WITH_SHORTCUT, key); |
| } else { |
| return l10n_util::GetStringUTF16( |
| IDS_EXTENSION_INSTALLED_BROWSER_ACTION_INFO); |
| } |
| break; |
| case ExtensionInstalledBubble::PAGE_ACTION: |
| if (!key.empty()) { |
| return l10n_util::GetStringFUTF16( |
| IDS_EXTENSION_INSTALLED_PAGE_ACTION_INFO_WITH_SHORTCUT, key); |
| } else { |
| return l10n_util::GetStringUTF16( |
| IDS_EXTENSION_INSTALLED_PAGE_ACTION_INFO); |
| } |
| break; |
| case ExtensionInstalledBubble::OMNIBOX_KEYWORD: |
| return l10n_util::GetStringFUTF16( |
| IDS_EXTENSION_INSTALLED_OMNIBOX_KEYWORD_INFO, key); |
| break; |
| default: |
| NOTREACHED(); |
| break; |
| } |
| return string16(); |
| } |
| |
| // Layout the signin promo at coordinates |offset_x| and |offset_y|. Returns |
| // the height (in pixels) of the promo UI. |
| int LayoutSigninPromo(int offset_x, int offset_y) { |
| sign_in_promo_lines_.clear(); |
| int height = 0; |
| gfx::Rect contents_area = GetContentsBounds(); |
| if (contents_area.IsEmpty()) |
| return height; |
| contents_area.set_width(kRightColumnWidth); |
| |
| string16 full_text = signin_promo_link_text_ + signin_promo_text_; |
| |
| // The link is the first item in the text. |
| const gfx::Size link_size = sign_in_link_->GetPreferredSize(); |
| sign_in_link_->SetBounds( |
| offset_x, offset_y, link_size.width(), link_size.height()); |
| |
| // Word-wrap the full label text. |
| const gfx::Font font; |
| std::vector<string16> lines; |
| ui::ElideRectangleText(full_text, font, contents_area.width(), |
| contents_area.height(), ui::ELIDE_LONG_WORDS, |
| &lines); |
| |
| gfx::Point position = gfx::Point( |
| contents_area.origin().x() + offset_x, |
| contents_area.origin().y() + offset_y + 1); |
| if (base::i18n::IsRTL()) { |
| position -= gfx::Vector2d( |
| 2 * views::kPanelHorizMargin + kHorizOuterMargin, 0); |
| } |
| |
| // Loop through the lines, creating a renderer for each. |
| for (std::vector<string16>::const_iterator it = lines.begin(); |
| it != lines.end(); ++it) { |
| gfx::RenderText* line = gfx::RenderText::CreateInstance(); |
| line->SetDirectionalityMode(gfx::DIRECTIONALITY_FROM_UI); |
| line->SetText(*it); |
| const gfx::Size size(contents_area.width(), |
| line->GetStringSize().height()); |
| line->SetDisplayRect(gfx::Rect(position, size)); |
| position.set_y(position.y() + size.height()); |
| sign_in_promo_lines_.push_back(line); |
| height += size.height(); |
| } |
| |
| // The link is drawn separately; make it transparent here to only draw once. |
| // The link always leads other text and is assumed to fit on the first line. |
| sign_in_promo_lines_.front()->ApplyColor(SK_ColorTRANSPARENT, |
| ui::Range(0, signin_promo_link_text_.size())); |
| |
| return height; |
| } |
| |
| virtual gfx::Size GetPreferredSize() OVERRIDE { |
| int width = kHorizOuterMargin; |
| width += kIconSize; |
| width += views::kPanelHorizMargin; |
| width += kRightColumnWidth; |
| width += 2 * views::kPanelHorizMargin; |
| width += kHorizOuterMargin; |
| |
| int height = kVertOuterMargin; |
| height += heading_->GetHeightForWidth(kRightColumnWidth); |
| height += kVertInnerMargin; |
| |
| if (flavors_ & HOW_TO_USE) { |
| height += how_to_use_->GetHeightForWidth(kRightColumnWidth); |
| height += kVertInnerMargin; |
| } |
| |
| if (flavors_ & HOW_TO_MANAGE) { |
| height += manage_->GetHeightForWidth(kRightColumnWidth); |
| height += kVertInnerMargin; |
| } |
| |
| if (flavors_ & SIGN_IN_PROMO && height_of_signin_promo_ > 0u) { |
| height += height_of_signin_promo_; |
| height += kVertInnerMargin; |
| } |
| |
| if (flavors_ & SHOW_KEYBINDING) { |
| height += manage_shortcut_->GetHeightForWidth(kRightColumnWidth); |
| height += kVertInnerMargin; |
| } |
| |
| return gfx::Size(width, std::max(height, kIconSize + 2 * kVertOuterMargin)); |
| } |
| |
| virtual void Layout() OVERRIDE { |
| int x = kHorizOuterMargin; |
| int y = kVertOuterMargin; |
| |
| icon_->SetBounds(x, y, kIconSize, kIconSize); |
| x += kIconSize; |
| x += views::kPanelHorizMargin; |
| |
| y += kRightcolumnVerticalShift; |
| heading_->SizeToFit(kRightColumnWidth); |
| heading_->SetX(x); |
| heading_->SetY(y); |
| y += heading_->height(); |
| y += kVertInnerMargin; |
| |
| if (flavors_ & HOW_TO_USE) { |
| how_to_use_->SizeToFit(kRightColumnWidth); |
| how_to_use_->SetX(x); |
| how_to_use_->SetY(y); |
| y += how_to_use_->height(); |
| y += kVertInnerMargin; |
| } |
| |
| if (flavors_ & HOW_TO_MANAGE) { |
| manage_->SizeToFit(kRightColumnWidth); |
| manage_->SetX(x); |
| manage_->SetY(y); |
| y += manage_->height(); |
| y += kVertInnerMargin; |
| } |
| |
| if (flavors_ & SIGN_IN_PROMO) { |
| height_of_signin_promo_ = LayoutSigninPromo(x, y); |
| y += height_of_signin_promo_; |
| y += kVertInnerMargin; |
| } |
| |
| if (flavors_ & SHOW_KEYBINDING) { |
| gfx::Size sz = manage_shortcut_->GetPreferredSize(); |
| manage_shortcut_->SetBounds(width() - 2 * kHorizOuterMargin - sz.width(), |
| y, |
| sz.width(), |
| sz.height()); |
| y += manage_shortcut_->height(); |
| y += kVertInnerMargin; |
| } |
| |
| gfx::Size sz; |
| x += kRightColumnWidth + 2 * views::kPanelHorizMargin + kHorizOuterMargin - |
| close_button_->GetPreferredSize().width(); |
| y = kVertOuterMargin; |
| sz = close_button_->GetPreferredSize(); |
| // x-1 & y-1 is just slop to get the close button visually aligned with the |
| // title text and bubble arrow. |
| close_button_->SetBounds(x - 1, y - 1, sz.width(), sz.height()); |
| } |
| |
| virtual void OnPaint(gfx::Canvas* canvas) OVERRIDE { |
| for (ScopedVector<gfx::RenderText>::const_iterator it = |
| sign_in_promo_lines_.begin(); |
| it != sign_in_promo_lines_.end(); ++it) |
| (*it)->Draw(canvas); |
| |
| views::View::OnPaint(canvas); |
| } |
| |
| // The browser we're associated with. |
| Browser* browser_; |
| |
| // The id of the extension just installed. |
| const std::string extension_id_; |
| |
| // The ExtensionInstalledBubble showing us. |
| ExtensionInstalledBubble* bubble_; |
| |
| // The string that contains the link text at the beginning of the sign-in |
| // promo text. |
| string16 signin_promo_link_text_; |
| // The remaining text of the sign-in promo text. |
| string16 signin_promo_text_; |
| |
| // A vector of RenderText objects representing the full sign-in promo |
| // paragraph as layed out within the bubble, but has the text of the link |
| // whited out so the link can be drawn in its place. |
| ScopedVector<gfx::RenderText> sign_in_promo_lines_; |
| |
| // The type of the bubble to show (Browser Action, Omnibox keyword, etc). |
| ExtensionInstalledBubble::BubbleType type_; |
| |
| // A bitmask containing the various flavors of bubble sections to show. |
| int flavors_; |
| |
| // The height, in pixels, of the sign-in promo. |
| size_t height_of_signin_promo_; |
| |
| views::ImageView* icon_; |
| views::Label* heading_; |
| views::Label* how_to_use_; |
| views::Link* sign_in_link_; |
| views::Label* manage_; |
| views::Link* manage_shortcut_; |
| views::ImageButton* close_button_; |
| |
| DISALLOW_COPY_AND_ASSIGN(InstalledBubbleContent); |
| }; |
| |
| void ExtensionInstalledBubble::Show(const Extension* extension, |
| Browser *browser, |
| const SkBitmap& icon) { |
| new ExtensionInstalledBubble(extension, browser, icon); |
| } |
| |
| ExtensionInstalledBubble::ExtensionInstalledBubble(const Extension* extension, |
| Browser *browser, |
| const SkBitmap& icon) |
| : extension_(extension), |
| browser_(browser), |
| icon_(icon), |
| animation_wait_retries_(0), |
| weak_factory_(this) { |
| if (!extensions::OmniboxInfo::GetKeyword(extension).empty()) |
| type_ = OMNIBOX_KEYWORD; |
| else if (extensions::ActionInfo::GetBrowserActionInfo(extension)) |
| type_ = BROWSER_ACTION; |
| else if (extensions::ActionInfo::GetPageActionInfo(extension) && |
| extensions::ActionInfo::IsVerboseInstallMessage(extension)) |
| type_ = PAGE_ACTION; |
| else |
| type_ = GENERIC; |
| |
| // |extension| has been initialized but not loaded at this point. We need |
| // to wait on showing the Bubble until not only the EXTENSION_LOADED gets |
| // fired, but all of the EXTENSION_LOADED Observers have run. Only then can we |
| // be sure that a BrowserAction or PageAction has had views created which we |
| // can inspect for the purpose of previewing of pointing to them. |
| registrar_.Add(this, chrome::NOTIFICATION_EXTENSION_LOADED, |
| content::Source<Profile>(browser->profile())); |
| registrar_.Add(this, chrome::NOTIFICATION_EXTENSION_UNLOADED, |
| content::Source<Profile>(browser->profile())); |
| registrar_.Add(this, chrome::NOTIFICATION_BROWSER_CLOSING, |
| content::Source<Browser>(browser)); |
| } |
| |
| ExtensionInstalledBubble::~ExtensionInstalledBubble() {} |
| |
| void ExtensionInstalledBubble::Observe( |
| int type, |
| const content::NotificationSource& source, |
| const content::NotificationDetails& details) { |
| if (type == chrome::NOTIFICATION_EXTENSION_LOADED) { |
| const Extension* extension = |
| content::Details<const Extension>(details).ptr(); |
| if (extension == extension_) { |
| animation_wait_retries_ = 0; |
| // PostTask to ourself to allow all EXTENSION_LOADED Observers to run. |
| base::MessageLoopForUI::current()->PostTask( |
| FROM_HERE, |
| base::Bind(&ExtensionInstalledBubble::ShowInternal, |
| weak_factory_.GetWeakPtr())); |
| } |
| } else if (type == chrome::NOTIFICATION_EXTENSION_UNLOADED) { |
| const Extension* extension = |
| content::Details<extensions::UnloadedExtensionInfo>(details)->extension; |
| if (extension == extension_) { |
| // Extension is going away, make sure ShowInternal won't be called. |
| weak_factory_.InvalidateWeakPtrs(); |
| extension_ = NULL; |
| } |
| } else if (type == chrome::NOTIFICATION_BROWSER_CLOSING) { |
| delete this; |
| } else { |
| NOTREACHED() << L"Received unexpected notification"; |
| } |
| } |
| |
| void ExtensionInstalledBubble::ShowInternal() { |
| BrowserView* browser_view = BrowserView::GetBrowserViewForBrowser(browser_); |
| extensions::ExtensionActionManager* extension_action_manager = |
| extensions::ExtensionActionManager::Get(browser_->profile()); |
| |
| views::View* reference_view = NULL; |
| if (type_ == BROWSER_ACTION) { |
| BrowserActionsContainer* container = |
| browser_view->GetToolbarView()->browser_actions(); |
| if (container->animating() && |
| animation_wait_retries_++ < kAnimationWaitMaxRetry) { |
| // We don't know where the view will be until the container has stopped |
| // animating, so check back in a little while. |
| base::MessageLoopForUI::current()->PostDelayedTask( |
| FROM_HERE, |
| base::Bind(&ExtensionInstalledBubble::ShowInternal, |
| weak_factory_.GetWeakPtr()), |
| base::TimeDelta::FromMilliseconds(kAnimationWaitTime)); |
| return; |
| } |
| reference_view = container->GetBrowserActionView( |
| extension_action_manager->GetBrowserAction(*extension_)); |
| // If the view is not visible then it is in the chevron, so point the |
| // install bubble to the chevron instead. If this is an incognito window, |
| // both could be invisible. |
| if (!reference_view || !reference_view->visible()) { |
| reference_view = container->chevron(); |
| if (!reference_view || !reference_view->visible()) |
| reference_view = NULL; // fall back to app menu below. |
| } |
| } else if (type_ == PAGE_ACTION) { |
| LocationBarView* location_bar_view = browser_view->GetLocationBarView(); |
| ExtensionAction* page_action = |
| extension_action_manager->GetPageAction(*extension_); |
| location_bar_view->SetPreviewEnabledPageAction(page_action, |
| true); // preview_enabled |
| reference_view = location_bar_view->GetPageActionView(page_action); |
| DCHECK(reference_view); |
| } else if (type_ == OMNIBOX_KEYWORD) { |
| LocationBarView* location_bar_view = browser_view->GetLocationBarView(); |
| reference_view = location_bar_view; |
| DCHECK(reference_view); |
| } |
| |
| // Default case. |
| if (reference_view == NULL) |
| reference_view = browser_view->GetToolbarView()->app_menu(); |
| set_anchor_view(reference_view); |
| |
| set_arrow(type_ == OMNIBOX_KEYWORD ? views::BubbleBorder::TOP_LEFT : |
| views::BubbleBorder::TOP_RIGHT); |
| SetLayoutManager(new views::FillLayout()); |
| AddChildView( |
| new InstalledBubbleContent(browser_, extension_, type_, &icon_, this)); |
| |
| views::BubbleDelegateView::CreateBubble(this); |
| |
| // The bubble widget is now the parent and owner of |this| and takes care of |
| // deletion when the bubble or browser go away. |
| registrar_.Remove(this, chrome::NOTIFICATION_BROWSER_CLOSING, |
| content::Source<Browser>(browser_)); |
| |
| StartFade(true); |
| } |
| |
| gfx::Rect ExtensionInstalledBubble::GetAnchorRect() { |
| // For omnibox keyword bubbles, move the arrow to point to the left edge |
| // of the omnibox, just to the right of the icon. |
| if (type_ == OMNIBOX_KEYWORD) { |
| LocationBarView* location_bar_view = |
| BrowserView::GetBrowserViewForBrowser(browser_)->GetLocationBarView(); |
| return gfx::Rect(location_bar_view->GetLocationEntryOrigin(), |
| gfx::Size(0, location_bar_view->location_entry_view()->height())); |
| } |
| return views::BubbleDelegateView::GetAnchorRect(); |
| } |
| |
| void ExtensionInstalledBubble::WindowClosing() { |
| if (extension_ && type_ == PAGE_ACTION) { |
| BrowserView* browser_view = BrowserView::GetBrowserViewForBrowser(browser_); |
| browser_view->GetLocationBarView()->SetPreviewEnabledPageAction( |
| extensions::ExtensionActionManager::Get(browser_->profile())-> |
| GetPageAction(*extension_), |
| false); // preview_enabled |
| } |
| } |