| // 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_install_dialog_view.h" |
| |
| #include <stddef.h> |
| #include <algorithm> |
| #include <string> |
| #include <utility> |
| #include <vector> |
| |
| #include "base/callback_helpers.h" |
| #include "base/command_line.h" |
| #include "base/compiler_specific.h" |
| #include "base/i18n/rtl.h" |
| #include "base/macros.h" |
| #include "base/metrics/histogram.h" |
| #include "base/strings/string_util.h" |
| #include "base/strings/utf_string_conversions.h" |
| #include "chrome/browser/browser_process.h" |
| #include "chrome/browser/extensions/api/experience_sampling_private/experience_sampling.h" |
| #include "chrome/browser/extensions/bundle_installer.h" |
| #include "chrome/browser/extensions/extension_install_prompt_show_params.h" |
| #include "chrome/browser/profiles/profile.h" |
| #include "chrome/browser/ui/browser.h" |
| #include "chrome/browser/ui/scoped_tabbed_browser_displayer.h" |
| #include "chrome/common/extensions/extension_constants.h" |
| #include "chrome/grit/generated_resources.h" |
| #include "chrome/installer/util/browser_distribution.h" |
| #include "components/constrained_window/constrained_window_views.h" |
| #include "content/public/browser/page_navigator.h" |
| #include "content/public/browser/web_contents.h" |
| #include "extensions/common/extension.h" |
| #include "extensions/common/extension_urls.h" |
| #include "ui/base/l10n/l10n_util.h" |
| #include "ui/base/resource/resource_bundle.h" |
| #include "ui/gfx/color_palette.h" |
| #include "ui/gfx/paint_vector_icon.h" |
| #include "ui/gfx/text_utils.h" |
| #include "ui/gfx/vector_icons_public.h" |
| #include "ui/native_theme/common_theme.h" |
| #include "ui/views/border.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/scroll_view.h" |
| #include "ui/views/controls/separator.h" |
| #include "ui/views/layout/box_layout.h" |
| #include "ui/views/layout/grid_layout.h" |
| #include "ui/views/layout/layout_constants.h" |
| #include "ui/views/widget/widget.h" |
| |
| using content::OpenURLParams; |
| using content::Referrer; |
| using extensions::BundleInstaller; |
| using extensions::ExperienceSamplingEvent; |
| |
| namespace { |
| |
| // Width of the bullet column in BulletedView. |
| const int kBulletWidth = 20; |
| |
| // Size of extension icon in top left of dialog. |
| const int kIconSize = 64; |
| |
| // Size of the icons of individual extensions for bundle installs. |
| const int kSmallIconSize = 32; |
| |
| // Padding between extension icon and title for bundle installs. |
| const int kSmallIconPadding = 6; |
| |
| // The maximum height of the scroll view before it will show a scrollbar. |
| const int kScrollViewMaxHeight = 250; |
| |
| // Width of the left column of the dialog when the extension requests |
| // permissions. |
| const int kPermissionsLeftColumnWidth = 250; |
| |
| // Width of the left column of the dialog when the extension requests no |
| // permissions. |
| const int kNoPermissionsLeftColumnWidth = 200; |
| |
| // Width of the left column for external install prompts. The text is long in |
| // this case, so make it wider than normal. |
| const int kExternalInstallLeftColumnWidth = 350; |
| |
| void AddResourceIcon(const gfx::ImageSkia* skia_image, void* data) { |
| views::View* parent = static_cast<views::View*>(data); |
| views::ImageView* image_view = new views::ImageView(); |
| image_view->SetImage(*skia_image); |
| parent->AddChildView(image_view); |
| } |
| |
| // Creates a string for displaying |message| to the user. If it has to look |
| // like a entry in a bullet point list, one is added. |
| base::string16 PrepareForDisplay(const base::string16& message, |
| bool bullet_point) { |
| return bullet_point ? l10n_util::GetStringFUTF16( |
| IDS_EXTENSION_PERMISSION_LINE, |
| message) : message; |
| } |
| |
| void ShowExtensionInstallDialogImpl( |
| ExtensionInstallPromptShowParams* show_params, |
| const ExtensionInstallPrompt::DoneCallback& done_callback, |
| scoped_ptr<ExtensionInstallPrompt::Prompt> prompt) { |
| DCHECK_CURRENTLY_ON(content::BrowserThread::UI); |
| bool use_tab_modal_dialog = prompt->ShouldUseTabModalDialog(); |
| ExtensionInstallDialogView* dialog = new ExtensionInstallDialogView( |
| show_params->profile(), show_params->GetParentWebContents(), |
| done_callback, std::move(prompt)); |
| if (use_tab_modal_dialog) { |
| content::WebContents* parent_web_contents = |
| show_params->GetParentWebContents(); |
| if (parent_web_contents) |
| constrained_window::ShowWebModalDialogViews(dialog, parent_web_contents); |
| } else { |
| constrained_window::CreateBrowserModalDialogViews( |
| dialog, show_params->GetParentWindow()) |
| ->Show(); |
| } |
| } |
| |
| // A custom scrollable view implementation for the dialog. |
| class CustomScrollableView : public views::View { |
| public: |
| CustomScrollableView() {} |
| ~CustomScrollableView() override {} |
| |
| // Called when one of the child elements has expanded/collapsed. |
| void ChildPreferredSizeChanged(views::View* child) override { |
| PreferredSizeChanged(); |
| } |
| |
| private: |
| void Layout() override { |
| SetBounds(x(), y(), width(), GetHeightForWidth(width())); |
| views::View::Layout(); |
| } |
| |
| DISALLOW_COPY_AND_ASSIGN(CustomScrollableView); |
| }; |
| |
| } // namespace |
| |
| BulletedView::BulletedView(views::View* view) { |
| views::GridLayout* layout = new views::GridLayout(this); |
| SetLayoutManager(layout); |
| views::ColumnSet* column_set = layout->AddColumnSet(0); |
| column_set->AddColumn(views::GridLayout::CENTER, |
| views::GridLayout::LEADING, |
| 0, |
| views::GridLayout::FIXED, |
| kBulletWidth, |
| 0); |
| column_set->AddColumn(views::GridLayout::LEADING, |
| views::GridLayout::LEADING, |
| 0, |
| views::GridLayout::USE_PREF, |
| 0, // No fixed width. |
| 0); |
| layout->StartRow(0, 0); |
| layout->AddView(new views::Label(PrepareForDisplay(base::string16(), true))); |
| layout->AddView(view); |
| } |
| |
| IconedView::IconedView(views::View* view, const gfx::ImageSkia& image) { |
| views::GridLayout* layout = new views::GridLayout(this); |
| SetLayoutManager(layout); |
| views::ColumnSet* column_set = layout->AddColumnSet(0); |
| column_set->AddColumn(views::GridLayout::CENTER, |
| views::GridLayout::LEADING, |
| 0, |
| views::GridLayout::FIXED, |
| kSmallIconSize, |
| 0); |
| column_set->AddPaddingColumn(0, kSmallIconPadding); |
| column_set->AddColumn(views::GridLayout::LEADING, |
| views::GridLayout::CENTER, |
| 0, |
| views::GridLayout::USE_PREF, |
| 0, // No fixed width. |
| 0); |
| layout->StartRow(0, 0); |
| |
| gfx::Size size(image.width(), image.height()); |
| if (size.width() > kSmallIconSize || size.height() > kSmallIconSize) |
| size = gfx::Size(kSmallIconSize, kSmallIconSize); |
| views::ImageView* image_view = new views::ImageView; |
| image_view->SetImage(image); |
| image_view->SetImageSize(size); |
| |
| layout->AddView(image_view); |
| layout->AddView(view); |
| } |
| |
| ExtensionInstallDialogView::ExtensionInstallDialogView( |
| Profile* profile, |
| content::PageNavigator* navigator, |
| const ExtensionInstallPrompt::DoneCallback& done_callback, |
| scoped_ptr<ExtensionInstallPrompt::Prompt> prompt) |
| : profile_(profile), |
| navigator_(navigator), |
| done_callback_(done_callback), |
| prompt_(std::move(prompt)), |
| container_(NULL), |
| scroll_view_(NULL), |
| handled_result_(false) { |
| InitView(); |
| } |
| |
| ExtensionInstallDialogView::~ExtensionInstallDialogView() { |
| if (!handled_result_ && !done_callback_.is_null()) { |
| base::ResetAndReturn(&done_callback_) |
| .Run(ExtensionInstallPrompt::Result::USER_CANCELED); |
| } |
| } |
| |
| void ExtensionInstallDialogView::InitView() { |
| // Possible grid layouts: |
| // With webstore data (inline install, external install, repair) |
| // w/ permissions no permissions |
| // +--------------+------+ +--------------+------+ |
| // | title | icon | | title | icon | |
| // +--------------| | +--------------| | |
| // | rating | | | rating | | |
| // +--------------| | +--------------| | |
| // | user_count | | | user_count | | |
| // +--------------| | +--------------| | |
| // | store_link | | | store_link | | |
| // +--------------+------+ +--------------+------+ |
| // | separator | | scroll_view (empty) | |
| // +---------------------+ +---------------------+ |
| // | scroll_view | |
| // +---------------------+ |
| // |
| // No webstore data (all other types) |
| // +--------------+------+ |
| // | title | icon | |
| // +--------------| | |
| // | scroll_view | | |
| // +--------------+------+ |
| // The scroll_view contains permissions (if there are any) and retained |
| // files/devices (if there are any; post-install-permissions prompt only). |
| int left_column_width = |
| (prompt_->ShouldShowPermissions() || prompt_->GetRetainedFileCount() > 0) |
| ? kPermissionsLeftColumnWidth |
| : kNoPermissionsLeftColumnWidth; |
| if (is_external_install()) |
| left_column_width = kExternalInstallLeftColumnWidth; |
| |
| int column_set_id = 0; |
| views::GridLayout* layout = CreateLayout(left_column_width, column_set_id); |
| |
| ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance(); |
| |
| if (prompt_->has_webstore_data()) { |
| layout->StartRow(0, column_set_id); |
| views::View* rating = new views::View(); |
| rating->SetLayoutManager(new views::BoxLayout( |
| views::BoxLayout::kHorizontal, 0, 0, 0)); |
| layout->AddView(rating); |
| prompt_->AppendRatingStars(AddResourceIcon, rating); |
| |
| const gfx::FontList& small_font_list = |
| rb.GetFontList(ui::ResourceBundle::SmallFont); |
| views::Label* rating_count = |
| new views::Label(prompt_->GetRatingCount(), small_font_list); |
| // Add some space between the stars and the rating count. |
| rating_count->SetBorder(views::Border::CreateEmptyBorder(0, 2, 0, 0)); |
| rating->AddChildView(rating_count); |
| |
| layout->StartRow(0, column_set_id); |
| views::Label* user_count = |
| new views::Label(prompt_->GetUserCount(), small_font_list); |
| user_count->SetAutoColorReadabilityEnabled(false); |
| user_count->SetEnabledColor(SK_ColorGRAY); |
| layout->AddView(user_count); |
| |
| layout->StartRow(0, column_set_id); |
| views::Link* store_link = new views::Link( |
| l10n_util::GetStringUTF16(IDS_EXTENSION_PROMPT_STORE_LINK)); |
| store_link->SetFontList(small_font_list); |
| store_link->set_listener(this); |
| layout->AddView(store_link); |
| |
| if (prompt_->ShouldShowPermissions()) { |
| layout->AddPaddingRow(0, views::kRelatedControlVerticalSpacing); |
| layout->StartRow(0, column_set_id); |
| layout->AddView(new views::Separator(views::Separator::HORIZONTAL), |
| 3, |
| 1, |
| views::GridLayout::FILL, |
| views::GridLayout::FILL); |
| } |
| } |
| |
| int content_width = left_column_width + views::kPanelHorizMargin + kIconSize; |
| |
| // Create the scrollable view which will contain the permissions and retained |
| // files/devices. |
| CustomScrollableView* scrollable = new CustomScrollableView(); |
| views::GridLayout* scroll_layout = new views::GridLayout(scrollable); |
| scrollable->SetLayoutManager(scroll_layout); |
| |
| views::ColumnSet* scrollable_column_set = |
| scroll_layout->AddColumnSet(column_set_id); |
| // If we have webstore data, there's a separator below it, so we can span the |
| // whole content width. Otherwise just use the width of the left column so |
| // that we don't overlap the icon. |
| int scrollable_width = prompt_->has_webstore_data() ? content_width |
| : left_column_width; |
| scrollable_column_set->AddColumn(views::GridLayout::LEADING, |
| views::GridLayout::LEADING, |
| 0, // no resizing |
| views::GridLayout::USE_PREF, |
| scrollable_width, |
| scrollable_width); |
| // Pad to the very right of the dialog, so the scrollbar will be on the edge. |
| int padding_width = |
| content_width + views::kButtonHEdgeMarginNew - scrollable_width; |
| scrollable_column_set->AddPaddingColumn(0, padding_width); |
| |
| layout->StartRow(0, column_set_id); |
| scroll_view_ = new views::ScrollView(); |
| scroll_view_->set_hide_horizontal_scrollbar(true); |
| scroll_view_->SetContents(scrollable); |
| layout->AddView(scroll_view_, 4, 1); |
| |
| if (is_bundle_install()) { |
| BundleInstaller::ItemList items = prompt_->bundle()->GetItemsWithState( |
| BundleInstaller::Item::STATE_PENDING); |
| scroll_layout->AddPaddingRow(0, views::kRelatedControlSmallVerticalSpacing); |
| for (const BundleInstaller::Item& item : items) { |
| scroll_layout->StartRow(0, column_set_id); |
| views::Label* extension_label = |
| new views::Label(item.GetNameForDisplay()); |
| extension_label->SetMultiLine(true); |
| extension_label->SetHorizontalAlignment(gfx::ALIGN_LEFT); |
| extension_label->SizeToFit( |
| scrollable_width - kSmallIconSize - kSmallIconPadding); |
| gfx::ImageSkia image = gfx::ImageSkia::CreateFrom1xBitmap(item.icon); |
| scroll_layout->AddView(new IconedView(extension_label, image)); |
| } |
| scroll_layout->AddPaddingRow(0, views::kRelatedControlVerticalSpacing); |
| } |
| |
| if (prompt_->ShouldShowPermissions()) { |
| bool has_permissions = |
| prompt_->GetPermissionCount( |
| ExtensionInstallPrompt::PermissionsType::ALL_PERMISSIONS) > 0; |
| if (has_permissions) { |
| AddPermissions( |
| scroll_layout, |
| rb, |
| column_set_id, |
| scrollable_width, |
| ExtensionInstallPrompt::PermissionsType::REGULAR_PERMISSIONS); |
| AddPermissions( |
| scroll_layout, |
| rb, |
| column_set_id, |
| scrollable_width, |
| ExtensionInstallPrompt::PermissionsType::WITHHELD_PERMISSIONS); |
| } else { |
| scroll_layout->AddPaddingRow(0, views::kRelatedControlVerticalSpacing); |
| scroll_layout->StartRow(0, column_set_id); |
| views::Label* permission_label = new views::Label( |
| l10n_util::GetStringUTF16(IDS_EXTENSION_NO_SPECIAL_PERMISSIONS)); |
| permission_label->SetMultiLine(true); |
| permission_label->SetHorizontalAlignment(gfx::ALIGN_LEFT); |
| permission_label->SizeToFit(scrollable_width); |
| scroll_layout->AddView(permission_label); |
| } |
| } |
| |
| if (prompt_->GetRetainedFileCount()) { |
| scroll_layout->AddPaddingRow(0, views::kRelatedControlVerticalSpacing); |
| |
| scroll_layout->StartRow(0, column_set_id); |
| views::Label* retained_files_header = |
| new views::Label(prompt_->GetRetainedFilesHeading()); |
| retained_files_header->SetMultiLine(true); |
| retained_files_header->SetHorizontalAlignment(gfx::ALIGN_LEFT); |
| retained_files_header->SizeToFit(scrollable_width); |
| scroll_layout->AddView(retained_files_header); |
| |
| scroll_layout->StartRow(0, column_set_id); |
| PermissionDetails details; |
| for (size_t i = 0; i < prompt_->GetRetainedFileCount(); ++i) { |
| details.push_back(prompt_->GetRetainedFile(i)); |
| } |
| ExpandableContainerView* issue_advice_view = |
| new ExpandableContainerView(this, |
| base::string16(), |
| details, |
| scrollable_width, |
| false); |
| scroll_layout->AddView(issue_advice_view); |
| } |
| |
| if (prompt_->GetRetainedDeviceCount()) { |
| scroll_layout->AddPaddingRow(0, views::kRelatedControlVerticalSpacing); |
| |
| scroll_layout->StartRow(0, column_set_id); |
| views::Label* retained_devices_header = |
| new views::Label(prompt_->GetRetainedDevicesHeading()); |
| retained_devices_header->SetMultiLine(true); |
| retained_devices_header->SetHorizontalAlignment(gfx::ALIGN_LEFT); |
| retained_devices_header->SizeToFit(scrollable_width); |
| scroll_layout->AddView(retained_devices_header); |
| |
| scroll_layout->StartRow(0, column_set_id); |
| PermissionDetails details; |
| for (size_t i = 0; i < prompt_->GetRetainedDeviceCount(); ++i) { |
| details.push_back(prompt_->GetRetainedDeviceMessageString(i)); |
| } |
| ExpandableContainerView* issue_advice_view = |
| new ExpandableContainerView(this, |
| base::string16(), |
| details, |
| scrollable_width, |
| false); |
| scroll_layout->AddView(issue_advice_view); |
| } |
| |
| DCHECK_GE(prompt_->type(), 0); |
| UMA_HISTOGRAM_ENUMERATION("Extensions.InstallPrompt.Type", |
| prompt_->type(), |
| ExtensionInstallPrompt::NUM_PROMPT_TYPES); |
| |
| scroll_view_->ClipHeightTo( |
| 0, |
| std::min(kScrollViewMaxHeight, scrollable->GetPreferredSize().height())); |
| |
| dialog_size_ = gfx::Size( |
| content_width + 2 * views::kButtonHEdgeMarginNew, |
| container_->GetPreferredSize().height()); |
| |
| std::string event_name = ExperienceSamplingEvent::kExtensionInstallDialog; |
| event_name.append( |
| ExtensionInstallPrompt::PromptTypeToString(prompt_->type())); |
| sampling_event_ = ExperienceSamplingEvent::Create(event_name); |
| } |
| |
| bool ExtensionInstallDialogView::AddPermissions( |
| views::GridLayout* layout, |
| ui::ResourceBundle& rb, |
| int column_set_id, |
| int left_column_width, |
| ExtensionInstallPrompt::PermissionsType perm_type) { |
| if (prompt_->GetPermissionCount(perm_type) == 0) |
| return false; |
| |
| layout->AddPaddingRow(0, views::kRelatedControlVerticalSpacing); |
| |
| layout->StartRow(0, column_set_id); |
| views::Label* permissions_header = NULL; |
| if (is_bundle_install()) { |
| // We need to pass the FontList in the constructor, rather than calling |
| // SetFontList later, because otherwise SizeToFit mis-judges the width |
| // of the line. |
| permissions_header = |
| new views::Label(prompt_->GetPermissionsHeading(perm_type), |
| rb.GetFontList(ui::ResourceBundle::MediumFont)); |
| } else { |
| permissions_header = |
| new views::Label(prompt_->GetPermissionsHeading(perm_type)); |
| } |
| permissions_header->SetMultiLine(true); |
| permissions_header->SetHorizontalAlignment(gfx::ALIGN_LEFT); |
| permissions_header->SizeToFit(left_column_width); |
| layout->AddView(permissions_header); |
| |
| for (size_t i = 0; i < prompt_->GetPermissionCount(perm_type); ++i) { |
| layout->AddPaddingRow(0, views::kRelatedControlVerticalSpacing); |
| layout->StartRow(0, column_set_id); |
| views::Label* permission_label = |
| new views::Label(prompt_->GetPermission(i, perm_type)); |
| |
| permission_label->SetMultiLine(true); |
| permission_label->SetHorizontalAlignment(gfx::ALIGN_LEFT); |
| permission_label->SizeToFit(left_column_width - kBulletWidth); |
| layout->AddView(new BulletedView(permission_label)); |
| |
| // If we have more details to provide, show them in collapsed form. |
| if (!prompt_->GetPermissionsDetails(i, perm_type).empty()) { |
| layout->StartRow(0, column_set_id); |
| PermissionDetails details; |
| details.push_back(PrepareForDisplay( |
| prompt_->GetPermissionsDetails(i, perm_type), false)); |
| ExpandableContainerView* details_container = |
| new ExpandableContainerView(this, |
| base::string16(), |
| details, |
| left_column_width, |
| true); |
| layout->AddView(details_container); |
| } |
| } |
| return true; |
| } |
| |
| views::GridLayout* ExtensionInstallDialogView::CreateLayout( |
| int left_column_width, |
| int column_set_id) { |
| container_ = new views::View(); |
| // This is basically views::GridLayout::CreatePanel, but without a top or |
| // right margin (we effectively get a top margin anyway from the empty dialog |
| // title, and we add an explicit padding column as a right margin below). |
| views::GridLayout* layout = new views::GridLayout(container_); |
| layout->SetInsets(0, views::kButtonHEdgeMarginNew, views::kPanelVertMargin, |
| 0); |
| container_->SetLayoutManager(layout); |
| AddChildView(container_); |
| |
| views::ColumnSet* column_set = layout->AddColumnSet(column_set_id); |
| column_set->AddColumn(views::GridLayout::LEADING, views::GridLayout::LEADING, |
| 0, // no resizing |
| views::GridLayout::USE_PREF, |
| 0, // no fixed width |
| left_column_width); |
| column_set->AddPaddingColumn(0, views::kPanelHorizMargin); |
| column_set->AddColumn(views::GridLayout::TRAILING, views::GridLayout::LEADING, |
| 0, // no resizing |
| views::GridLayout::USE_PREF, |
| 0, // no fixed width |
| kIconSize); |
| column_set->AddPaddingColumn(0, views::kButtonHEdgeMarginNew); |
| |
| layout->StartRow(0, column_set_id); |
| views::Label* title = |
| new views::Label(prompt_->GetDialogTitle(), |
| ui::ResourceBundle::GetSharedInstance().GetFontList( |
| ui::ResourceBundle::MediumFont)); |
| title->SetMultiLine(true); |
| title->SetHorizontalAlignment(gfx::ALIGN_LEFT); |
| title->SizeToFit(left_column_width); |
| layout->AddView(title); |
| |
| // Scale down to icon size, but allow smaller icons (don't scale up). |
| const gfx::ImageSkia* image = prompt_->icon().ToImageSkia(); |
| gfx::Size size(image->width(), image->height()); |
| if (size.width() > kIconSize || size.height() > kIconSize) |
| size = gfx::Size(kIconSize, kIconSize); |
| views::ImageView* icon = new views::ImageView(); |
| icon->SetImageSize(size); |
| icon->SetImage(*image); |
| |
| int icon_row_span = 1; // Always span the title. |
| if (prompt_->has_webstore_data()) { |
| // Also span the rating, user_count and store_link rows. |
| icon_row_span += 3; |
| // Note: Do not span the permissions here, there's a separator in between! |
| } else { |
| // Also span the scrollable container with permissions, retained files etc. |
| icon_row_span += 1; |
| } |
| layout->AddView(icon, 1, icon_row_span); |
| |
| return layout; |
| } |
| |
| int ExtensionInstallDialogView::GetDialogButtons() const { |
| int buttons = prompt_->GetDialogButtons(); |
| // Simply having just an OK button is *not* supported. See comment on function |
| // GetDialogButtons in dialog_delegate.h for reasons. |
| DCHECK_GT(buttons & ui::DIALOG_BUTTON_CANCEL, 0); |
| return buttons; |
| } |
| |
| base::string16 ExtensionInstallDialogView::GetDialogButtonLabel( |
| ui::DialogButton button) const { |
| switch (button) { |
| case ui::DIALOG_BUTTON_OK: |
| return prompt_->GetAcceptButtonLabel(); |
| case ui::DIALOG_BUTTON_CANCEL: |
| return prompt_->GetAbortButtonLabel(); |
| default: |
| NOTREACHED(); |
| return base::string16(); |
| } |
| } |
| |
| int ExtensionInstallDialogView::GetDefaultDialogButton() const { |
| return ui::DIALOG_BUTTON_CANCEL; |
| } |
| |
| bool ExtensionInstallDialogView::Cancel() { |
| if (handled_result_) |
| return true; |
| |
| handled_result_ = true; |
| UpdateInstallResultHistogram(false); |
| if (sampling_event_) |
| sampling_event_->CreateUserDecisionEvent(ExperienceSamplingEvent::kDeny); |
| base::ResetAndReturn(&done_callback_) |
| .Run(ExtensionInstallPrompt::Result::USER_CANCELED); |
| return true; |
| } |
| |
| bool ExtensionInstallDialogView::Accept() { |
| DCHECK(!handled_result_); |
| |
| handled_result_ = true; |
| UpdateInstallResultHistogram(true); |
| if (sampling_event_) |
| sampling_event_->CreateUserDecisionEvent(ExperienceSamplingEvent::kProceed); |
| base::ResetAndReturn(&done_callback_) |
| .Run(ExtensionInstallPrompt::Result::ACCEPTED); |
| return true; |
| } |
| |
| ui::ModalType ExtensionInstallDialogView::GetModalType() const { |
| return prompt_->ShouldUseTabModalDialog() ? ui::MODAL_TYPE_CHILD |
| : ui::MODAL_TYPE_WINDOW; |
| } |
| |
| void ExtensionInstallDialogView::LinkClicked(views::Link* source, |
| int event_flags) { |
| GURL store_url(extension_urls::GetWebstoreItemDetailURLPrefix() + |
| prompt_->extension()->id()); |
| OpenURLParams params( |
| store_url, Referrer(), NEW_FOREGROUND_TAB, |
| ui::PAGE_TRANSITION_LINK, |
| false); |
| |
| if (navigator_) { |
| navigator_->OpenURL(params); |
| } else { |
| chrome::ScopedTabbedBrowserDisplayer displayer(profile_); |
| displayer.browser()->OpenURL(params); |
| } |
| GetWidget()->Close(); |
| } |
| |
| void ExtensionInstallDialogView::Layout() { |
| container_->SetBounds(0, 0, width(), height()); |
| DialogDelegateView::Layout(); |
| } |
| |
| gfx::Size ExtensionInstallDialogView::GetPreferredSize() const { |
| return dialog_size_; |
| } |
| |
| void ExtensionInstallDialogView::UpdateInstallResultHistogram(bool accepted) |
| const { |
| if (prompt_->type() == ExtensionInstallPrompt::INSTALL_PROMPT) |
| UMA_HISTOGRAM_BOOLEAN("Extensions.InstallPrompt.Accepted", accepted); |
| } |
| |
| // ExpandableContainerView::DetailsView ---------------------------------------- |
| |
| ExpandableContainerView::DetailsView::DetailsView(int horizontal_space, |
| bool parent_bulleted) |
| : layout_(new views::GridLayout(this)), |
| state_(0) { |
| SetLayoutManager(layout_); |
| views::ColumnSet* column_set = layout_->AddColumnSet(0); |
| // If the parent is using bullets for its items, then a padding of one unit |
| // will make the child item (which has no bullet) look like a sibling of its |
| // parent. Therefore increase the indentation by one more unit to show that it |
| // is in fact a child item (with no missing bullet) and not a sibling. |
| int padding = |
| views::kRelatedControlHorizontalSpacing * (parent_bulleted ? 2 : 1); |
| column_set->AddPaddingColumn(0, padding); |
| column_set->AddColumn(views::GridLayout::LEADING, |
| views::GridLayout::LEADING, |
| 0, |
| views::GridLayout::FIXED, |
| horizontal_space - padding, |
| 0); |
| } |
| |
| void ExpandableContainerView::DetailsView::AddDetail( |
| const base::string16& detail) { |
| layout_->StartRowWithPadding(0, 0, |
| 0, views::kRelatedControlSmallVerticalSpacing); |
| views::Label* detail_label = |
| new views::Label(PrepareForDisplay(detail, false)); |
| detail_label->SetMultiLine(true); |
| detail_label->SetHorizontalAlignment(gfx::ALIGN_LEFT); |
| layout_->AddView(detail_label); |
| } |
| |
| gfx::Size ExpandableContainerView::DetailsView::GetPreferredSize() const { |
| gfx::Size size = views::View::GetPreferredSize(); |
| return gfx::Size(size.width(), size.height() * state_); |
| } |
| |
| void ExpandableContainerView::DetailsView::AnimateToState(double state) { |
| state_ = state; |
| PreferredSizeChanged(); |
| SchedulePaint(); |
| } |
| |
| // ExpandableContainerView ----------------------------------------------------- |
| |
| ExpandableContainerView::ExpandableContainerView( |
| ExtensionInstallDialogView* owner, |
| const base::string16& description, |
| const PermissionDetails& details, |
| int horizontal_space, |
| bool parent_bulleted) |
| : owner_(owner), |
| details_view_(NULL), |
| slide_animation_(this), |
| more_details_(NULL), |
| arrow_toggle_(NULL), |
| expanded_(false) { |
| views::GridLayout* layout = new views::GridLayout(this); |
| SetLayoutManager(layout); |
| int column_set_id = 0; |
| views::ColumnSet* column_set = layout->AddColumnSet(column_set_id); |
| column_set->AddColumn(views::GridLayout::LEADING, |
| views::GridLayout::LEADING, |
| 0, |
| views::GridLayout::USE_PREF, |
| 0, |
| 0); |
| if (!description.empty()) { |
| layout->StartRow(0, column_set_id); |
| |
| views::Label* description_label = new views::Label(description); |
| description_label->SetMultiLine(true); |
| description_label->SetHorizontalAlignment(gfx::ALIGN_LEFT); |
| description_label->SizeToFit(horizontal_space); |
| layout->AddView(new BulletedView(description_label)); |
| } |
| |
| if (details.empty()) |
| return; |
| |
| details_view_ = new DetailsView(horizontal_space, parent_bulleted); |
| |
| layout->StartRow(0, column_set_id); |
| layout->AddView(details_view_); |
| |
| for (size_t i = 0; i < details.size(); ++i) |
| details_view_->AddDetail(details[i]); |
| |
| // Make sure the link width column is as wide as needed for both Show and |
| // Hide details, so that the arrow doesn't shift horizontally when we |
| // toggle. |
| views::Link* link = new views::Link( |
| l10n_util::GetStringUTF16(IDS_EXTENSIONS_HIDE_DETAILS)); |
| int link_col_width = link->GetPreferredSize().width(); |
| link->SetText(l10n_util::GetStringUTF16(IDS_EXTENSIONS_SHOW_DETAILS)); |
| link_col_width = std::max(link_col_width, link->GetPreferredSize().width()); |
| |
| column_set = layout->AddColumnSet(++column_set_id); |
| // Padding to the left of the More Details column. If the parent is using |
| // bullets for its items, then a padding of one unit will make the child |
| // item (which has no bullet) look like a sibling of its parent. Therefore |
| // increase the indentation by one more unit to show that it is in fact a |
| // child item (with no missing bullet) and not a sibling. |
| column_set->AddPaddingColumn( |
| 0, views::kRelatedControlHorizontalSpacing * (parent_bulleted ? 2 : 1)); |
| // The More Details column. |
| column_set->AddColumn(views::GridLayout::LEADING, |
| views::GridLayout::LEADING, |
| 0, |
| views::GridLayout::FIXED, |
| link_col_width, |
| link_col_width); |
| // The Up/Down arrow column. |
| column_set->AddColumn(views::GridLayout::LEADING, |
| views::GridLayout::TRAILING, |
| 0, |
| views::GridLayout::USE_PREF, |
| 0, |
| 0); |
| |
| // Add the More Details link. |
| layout->StartRow(0, column_set_id); |
| more_details_ = link; |
| more_details_->set_listener(this); |
| more_details_->SetHorizontalAlignment(gfx::ALIGN_LEFT); |
| layout->AddView(more_details_); |
| |
| // Add the arrow after the More Details link. |
| arrow_toggle_ = new views::ImageButton(this); |
| UpdateArrowToggle(false); |
| layout->AddView(arrow_toggle_); |
| } |
| |
| ExpandableContainerView::~ExpandableContainerView() { |
| } |
| |
| void ExpandableContainerView::ButtonPressed( |
| views::Button* sender, const ui::Event& event) { |
| ToggleDetailLevel(); |
| } |
| |
| void ExpandableContainerView::LinkClicked( |
| views::Link* source, int event_flags) { |
| ToggleDetailLevel(); |
| } |
| |
| void ExpandableContainerView::AnimationProgressed( |
| const gfx::Animation* animation) { |
| DCHECK_EQ(&slide_animation_, animation); |
| if (details_view_) |
| details_view_->AnimateToState(animation->GetCurrentValue()); |
| } |
| |
| void ExpandableContainerView::AnimationEnded(const gfx::Animation* animation) { |
| if (arrow_toggle_) |
| UpdateArrowToggle(animation->GetCurrentValue() != 0.0); |
| if (more_details_) { |
| more_details_->SetText(expanded_ ? |
| l10n_util::GetStringUTF16(IDS_EXTENSIONS_HIDE_DETAILS) : |
| l10n_util::GetStringUTF16(IDS_EXTENSIONS_SHOW_DETAILS)); |
| } |
| } |
| |
| void ExpandableContainerView::ChildPreferredSizeChanged(views::View* child) { |
| PreferredSizeChanged(); |
| } |
| |
| void ExpandableContainerView::ToggleDetailLevel() { |
| expanded_ = !expanded_; |
| |
| if (slide_animation_.IsShowing()) |
| slide_animation_.Hide(); |
| else |
| slide_animation_.Show(); |
| } |
| |
| void ExpandableContainerView::UpdateArrowToggle(bool expanded) { |
| gfx::ImageSkia icon = gfx::CreateVectorIcon( |
| expanded ? gfx::VectorIconId::FIND_PREV : gfx::VectorIconId::FIND_NEXT, |
| 16, gfx::kChromeIconGrey); |
| arrow_toggle_->SetImage(views::Button::STATE_NORMAL, &icon); |
| } |
| |
| // static |
| ExtensionInstallPrompt::ShowDialogCallback |
| ExtensionInstallPrompt::GetDefaultShowDialogCallback() { |
| return base::Bind(&ShowExtensionInstallDialogImpl); |
| } |