1 // Copyright (c) 2012 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.
5 #include "chrome/browser/ui/views/extensions/extension_install_dialog_view.h"
9 #include "base/basictypes.h"
10 #include "base/command_line.h"
11 #include "base/compiler_specific.h"
12 #include "base/i18n/rtl.h"
13 #include "base/metrics/histogram.h"
14 #include "base/strings/string_util.h"
15 #include "base/strings/utf_string_conversions.h"
16 #include "chrome/browser/browser_process.h"
17 #include "chrome/browser/extensions/api/experience_sampling_private/experience_sampling.h"
18 #include "chrome/browser/extensions/bundle_installer.h"
19 #include "chrome/browser/extensions/extension_install_prompt_show_params.h"
20 #include "chrome/browser/profiles/profile.h"
21 #include "chrome/browser/profiles/profile_manager.h"
22 #include "chrome/browser/ui/browser.h"
23 #include "chrome/browser/ui/scoped_tabbed_browser_displayer.h"
24 #include "chrome/common/extensions/extension_constants.h"
25 #include "chrome/grit/generated_resources.h"
26 #include "chrome/installer/util/browser_distribution.h"
27 #include "components/constrained_window/constrained_window_views.h"
28 #include "content/public/browser/page_navigator.h"
29 #include "content/public/browser/web_contents.h"
30 #include "extensions/common/extension.h"
31 #include "extensions/common/extension_urls.h"
32 #include "grit/theme_resources.h"
33 #include "ui/base/l10n/l10n_util.h"
34 #include "ui/base/resource/resource_bundle.h"
35 #include "ui/gfx/text_utils.h"
36 #include "ui/views/background.h"
37 #include "ui/views/border.h"
38 #include "ui/views/controls/button/checkbox.h"
39 #include "ui/views/controls/button/image_button.h"
40 #include "ui/views/controls/button/label_button.h"
41 #include "ui/views/controls/image_view.h"
42 #include "ui/views/controls/label.h"
43 #include "ui/views/controls/link.h"
44 #include "ui/views/controls/scroll_view.h"
45 #include "ui/views/controls/separator.h"
46 #include "ui/views/layout/box_layout.h"
47 #include "ui/views/layout/grid_layout.h"
48 #include "ui/views/layout/layout_constants.h"
49 #include "ui/views/widget/widget.h"
50 #include "ui/views/window/dialog_client_view.h"
52 using content::OpenURLParams
;
53 using content::Referrer
;
54 using extensions::BundleInstaller
;
55 using extensions::ExperienceSamplingEvent
;
59 // Width of the bullet column in BulletedView.
60 const int kBulletWidth
= 20;
62 // Size of extension icon in top left of dialog.
63 const int kIconSize
= 64;
65 // We offset the icon a little bit from the right edge of the dialog, to make it
66 // align with the button below it.
67 const int kIconOffset
= 16;
69 // Size of the icons of individual extensions for bundle installs.
70 const int kSmallIconSize
= 32;
72 // Padding between extension icon and title for bundle installs.
73 const int kSmallIconPadding
= 6;
75 // The dialog will resize based on its content, but this sets a maximum height
76 // before overflowing a scrollbar.
77 const int kDialogMaxHeight
= 300;
79 // Width of the left column of the dialog when the extension requests
81 const int kPermissionsLeftColumnWidth
= 250;
83 // Width of the left column of the dialog when the extension requests no
85 const int kNoPermissionsLeftColumnWidth
= 200;
87 // Width of the left column for external install prompts. The text is long in
88 // this case, so make it wider than normal.
89 const int kExternalInstallLeftColumnWidth
= 350;
91 void AddResourceIcon(const gfx::ImageSkia
* skia_image
, void* data
) {
92 views::View
* parent
= static_cast<views::View
*>(data
);
93 views::ImageView
* image_view
= new views::ImageView();
94 image_view
->SetImage(*skia_image
);
95 parent
->AddChildView(image_view
);
98 // Creates a string for displaying |message| to the user. If it has to look
99 // like a entry in a bullet point list, one is added.
100 base::string16
PrepareForDisplay(const base::string16
& message
,
102 return bullet_point
? l10n_util::GetStringFUTF16(
103 IDS_EXTENSION_PERMISSION_LINE
,
109 BulletedView::BulletedView(views::View
* view
) {
110 views::GridLayout
* layout
= new views::GridLayout(this);
111 SetLayoutManager(layout
);
112 views::ColumnSet
* column_set
= layout
->AddColumnSet(0);
113 column_set
->AddColumn(views::GridLayout::CENTER
,
114 views::GridLayout::LEADING
,
116 views::GridLayout::FIXED
,
119 column_set
->AddColumn(views::GridLayout::LEADING
,
120 views::GridLayout::LEADING
,
122 views::GridLayout::USE_PREF
,
123 0, // No fixed width.
125 layout
->StartRow(0, 0);
126 layout
->AddView(new views::Label(PrepareForDisplay(base::string16(), true)));
127 layout
->AddView(view
);
130 IconedView::IconedView(views::View
* view
, const gfx::ImageSkia
& image
) {
131 views::GridLayout
* layout
= new views::GridLayout(this);
132 SetLayoutManager(layout
);
133 views::ColumnSet
* column_set
= layout
->AddColumnSet(0);
134 column_set
->AddColumn(views::GridLayout::CENTER
,
135 views::GridLayout::LEADING
,
137 views::GridLayout::FIXED
,
140 column_set
->AddPaddingColumn(0, kSmallIconPadding
);
141 column_set
->AddColumn(views::GridLayout::LEADING
,
142 views::GridLayout::CENTER
,
144 views::GridLayout::USE_PREF
,
145 0, // No fixed width.
147 layout
->StartRow(0, 0);
149 gfx::Size
size(image
.width(), image
.height());
150 if (size
.width() > kSmallIconSize
|| size
.height() > kSmallIconSize
)
151 size
= gfx::Size(kSmallIconSize
, kSmallIconSize
);
152 views::ImageView
* image_view
= new views::ImageView
;
153 image_view
->SetImage(image
);
154 image_view
->SetImageSize(size
);
156 layout
->AddView(image_view
);
157 layout
->AddView(view
);
160 void ShowExtensionInstallDialogImpl(
161 ExtensionInstallPromptShowParams
* show_params
,
162 ExtensionInstallPrompt::Delegate
* delegate
,
163 scoped_refptr
<ExtensionInstallPrompt::Prompt
> prompt
) {
164 DCHECK_CURRENTLY_ON(content::BrowserThread::UI
);
165 ExtensionInstallDialogView
* dialog
=
166 new ExtensionInstallDialogView(show_params
->profile(),
167 show_params
->GetParentWebContents(),
170 constrained_window::CreateBrowserModalDialogViews(
171 dialog
, show_params
->GetParentWindow())->Show();
174 CustomScrollableView::CustomScrollableView() {}
175 CustomScrollableView::~CustomScrollableView() {}
177 void CustomScrollableView::Layout() {
178 SetBounds(x(), y(), width(), GetHeightForWidth(width()));
179 views::View::Layout();
182 ExtensionInstallDialogView::ExtensionInstallDialogView(
184 content::PageNavigator
* navigator
,
185 ExtensionInstallPrompt::Delegate
* delegate
,
186 scoped_refptr
<ExtensionInstallPrompt::Prompt
> prompt
)
188 navigator_(navigator
),
193 handled_result_(false) {
197 ExtensionInstallDialogView::~ExtensionInstallDialogView() {
198 if (!handled_result_
)
199 delegate_
->InstallUIAbort(true);
202 void ExtensionInstallDialogView::InitView() {
203 // Possible grid layouts:
205 // w/ permissions no permissions
206 // +--------------------+------+ +--------------+------+
207 // | title | icon | | title | icon |
208 // +--------------------| | +--------------| |
209 // | rating | | | rating | |
210 // +--------------------| | +--------------| |
211 // | user_count | | | user_count | |
212 // +--------------------| | +--------------| |
213 // | store_link | | | store_link | |
214 // +--------------------+------+ +--------------+------+
216 // +--------------------+------+
217 // | permissions_header | |
218 // +--------------------+------+
219 // | * permission1 | |
220 // +--------------------+------+
221 // | * permission2 | |
222 // +--------------------+------+
225 // w/ permissions no permissions
226 // +--------------------+------+ +--------------+------+
227 // | title | icon | | title | icon |
228 // +--------------------| | +--------------+------+
229 // | permissions_header | |
230 // +--------------------| |
231 // | * permission1 | |
232 // +--------------------| |
233 // | * permission2 | |
234 // +--------------------+------+
235 int left_column_width
=
236 (prompt_
->ShouldShowPermissions() + prompt_
->GetRetainedFileCount()) > 0
237 ? kPermissionsLeftColumnWidth
238 : kNoPermissionsLeftColumnWidth
;
239 if (is_external_install())
240 left_column_width
= kExternalInstallLeftColumnWidth
;
242 scroll_view_
= new views::ScrollView();
243 scroll_view_
->set_hide_horizontal_scrollbar(true);
244 AddChildView(scroll_view_
);
246 int column_set_id
= 0;
247 // Create the full scrollable view which will contain all the information
248 // including the permissions.
249 scrollable_
= new CustomScrollableView();
250 views::GridLayout
* layout
=
251 CreateLayout(scrollable_
, left_column_width
, column_set_id
);
252 ui::ResourceBundle
& rb
= ui::ResourceBundle::GetSharedInstance();
254 scroll_view_
->SetContents(scrollable_
);
256 int dialog_width
= left_column_width
+ 2 * views::kPanelHorizMargin
;
257 dialog_width
+= views::kPanelHorizMargin
+ kIconSize
+ kIconOffset
;
259 if (prompt_
->has_webstore_data()) {
260 layout
->StartRow(0, column_set_id
);
261 views::View
* rating
= new views::View();
262 rating
->SetLayoutManager(new views::BoxLayout(
263 views::BoxLayout::kHorizontal
, 0, 0, 0));
264 layout
->AddView(rating
);
265 prompt_
->AppendRatingStars(AddResourceIcon
, rating
);
267 const gfx::FontList
& small_font_list
=
268 rb
.GetFontList(ui::ResourceBundle::SmallFont
);
269 views::Label
* rating_count
=
270 new views::Label(prompt_
->GetRatingCount(), small_font_list
);
271 // Add some space between the stars and the rating count.
272 rating_count
->SetBorder(views::Border::CreateEmptyBorder(0, 2, 0, 0));
273 rating
->AddChildView(rating_count
);
275 layout
->StartRow(0, column_set_id
);
276 views::Label
* user_count
=
277 new views::Label(prompt_
->GetUserCount(), small_font_list
);
278 user_count
->SetAutoColorReadabilityEnabled(false);
279 user_count
->SetEnabledColor(SK_ColorGRAY
);
280 layout
->AddView(user_count
);
282 layout
->StartRow(0, column_set_id
);
283 views::Link
* store_link
= new views::Link(
284 l10n_util::GetStringUTF16(IDS_EXTENSION_PROMPT_STORE_LINK
));
285 store_link
->SetFontList(small_font_list
);
286 store_link
->set_listener(this);
287 layout
->AddView(store_link
);
290 if (is_bundle_install()) {
291 BundleInstaller::ItemList items
= prompt_
->bundle()->GetItemsWithState(
292 BundleInstaller::Item::STATE_PENDING
);
293 layout
->AddPaddingRow(0, views::kRelatedControlSmallVerticalSpacing
);
294 for (const BundleInstaller::Item
& item
: items
) {
295 layout
->StartRow(0, column_set_id
);
296 views::Label
* extension_label
=
297 new views::Label(item
.GetNameForDisplay());
298 extension_label
->SetMultiLine(true);
299 extension_label
->SetHorizontalAlignment(gfx::ALIGN_LEFT
);
300 extension_label
->SizeToFit(
301 left_column_width
- kSmallIconSize
- kSmallIconPadding
);
302 gfx::ImageSkia image
= gfx::ImageSkia::CreateFrom1xBitmap(item
.icon
);
303 layout
->AddView(new IconedView(extension_label
, image
));
305 layout
->AddPaddingRow(0, views::kRelatedControlVerticalSpacing
);
308 bool has_permissions
=
309 prompt_
->GetPermissionCount(
310 ExtensionInstallPrompt::PermissionsType::ALL_PERMISSIONS
) > 0;
311 if (prompt_
->ShouldShowPermissions()) {
317 ExtensionInstallPrompt::PermissionsType::REGULAR_PERMISSIONS
);
323 ExtensionInstallPrompt::PermissionsType::WITHHELD_PERMISSIONS
);
324 if (!has_permissions
) {
325 layout
->AddPaddingRow(0, views::kRelatedControlVerticalSpacing
);
326 layout
->StartRow(0, column_set_id
);
327 views::Label
* permission_label
= new views::Label(
328 l10n_util::GetStringUTF16(IDS_EXTENSION_NO_SPECIAL_PERMISSIONS
));
329 permission_label
->SetMultiLine(true);
330 permission_label
->SetHorizontalAlignment(gfx::ALIGN_LEFT
);
331 permission_label
->SizeToFit(left_column_width
);
332 layout
->AddView(permission_label
);
336 int space_for_files_and_devices
= left_column_width
;
337 if (prompt_
->GetRetainedFileCount() || prompt_
->GetRetainedDeviceCount()) {
338 // Slide in under the permissions, if there are any. If there are either,
339 // the retained files and devices prompts stretch all the way to the right
340 // of the dialog. If there are no permissions, the retained files and
341 // devices prompts just take up the left column.
343 if (has_permissions
) {
344 space_for_files_and_devices
+= kIconSize
;
345 views::ColumnSet
* column_set
= layout
->AddColumnSet(++column_set_id
);
346 column_set
->AddColumn(views::GridLayout::FILL
,
347 views::GridLayout::FILL
,
349 views::GridLayout::USE_PREF
,
351 space_for_files_and_devices
);
355 if (prompt_
->GetRetainedFileCount()) {
356 layout
->AddPaddingRow(0, views::kRelatedControlVerticalSpacing
);
358 layout
->StartRow(0, column_set_id
);
359 views::Label
* retained_files_header
=
360 new views::Label(prompt_
->GetRetainedFilesHeading());
361 retained_files_header
->SetMultiLine(true);
362 retained_files_header
->SetHorizontalAlignment(gfx::ALIGN_LEFT
);
363 retained_files_header
->SizeToFit(space_for_files_and_devices
);
364 layout
->AddView(retained_files_header
);
366 layout
->StartRow(0, column_set_id
);
367 PermissionDetails details
;
368 for (size_t i
= 0; i
< prompt_
->GetRetainedFileCount(); ++i
) {
369 details
.push_back(prompt_
->GetRetainedFile(i
));
371 ExpandableContainerView
* issue_advice_view
=
372 new ExpandableContainerView(this,
375 space_for_files_and_devices
,
377 layout
->AddView(issue_advice_view
);
380 if (prompt_
->GetRetainedDeviceCount()) {
381 layout
->AddPaddingRow(0, views::kRelatedControlVerticalSpacing
);
383 layout
->StartRow(0, column_set_id
);
384 views::Label
* retained_devices_header
=
385 new views::Label(prompt_
->GetRetainedDevicesHeading());
386 retained_devices_header
->SetMultiLine(true);
387 retained_devices_header
->SetHorizontalAlignment(gfx::ALIGN_LEFT
);
388 retained_devices_header
->SizeToFit(space_for_files_and_devices
);
389 layout
->AddView(retained_devices_header
);
391 layout
->StartRow(0, column_set_id
);
392 PermissionDetails details
;
393 for (size_t i
= 0; i
< prompt_
->GetRetainedDeviceCount(); ++i
) {
394 details
.push_back(prompt_
->GetRetainedDeviceMessageString(i
));
396 ExpandableContainerView
* issue_advice_view
=
397 new ExpandableContainerView(this,
400 space_for_files_and_devices
,
402 layout
->AddView(issue_advice_view
);
405 DCHECK(prompt_
->type() >= 0);
406 UMA_HISTOGRAM_ENUMERATION("Extensions.InstallPrompt.Type",
408 ExtensionInstallPrompt::NUM_PROMPT_TYPES
);
410 gfx::Size scrollable_size
= scrollable_
->GetPreferredSize();
411 scrollable_
->SetBoundsRect(gfx::Rect(scrollable_size
));
412 dialog_size_
= gfx::Size(
414 std::min(scrollable_size
.height(), kDialogMaxHeight
));
416 std::string event_name
= ExperienceSamplingEvent::kExtensionInstallDialog
;
418 ExtensionInstallPrompt::PromptTypeToString(prompt_
->type()));
419 sampling_event_
= ExperienceSamplingEvent::Create(event_name
);
422 bool ExtensionInstallDialogView::AddPermissions(
423 views::GridLayout
* layout
,
424 ui::ResourceBundle
& rb
,
426 int left_column_width
,
427 ExtensionInstallPrompt::PermissionsType perm_type
) {
428 if (prompt_
->GetPermissionCount(perm_type
) == 0)
431 layout
->AddPaddingRow(0, views::kRelatedControlVerticalSpacing
);
432 if (prompt_
->has_webstore_data()) {
433 layout
->StartRow(0, column_set_id
);
434 layout
->AddView(new views::Separator(views::Separator::HORIZONTAL
),
437 views::GridLayout::FILL
,
438 views::GridLayout::FILL
);
439 layout
->AddPaddingRow(0, views::kRelatedControlVerticalSpacing
);
442 layout
->StartRow(0, column_set_id
);
443 views::Label
* permissions_header
= NULL
;
444 if (is_bundle_install()) {
445 // We need to pass the FontList in the constructor, rather than calling
446 // SetFontList later, because otherwise SizeToFit mis-judges the width
449 new views::Label(prompt_
->GetPermissionsHeading(perm_type
),
450 rb
.GetFontList(ui::ResourceBundle::MediumFont
));
453 new views::Label(prompt_
->GetPermissionsHeading(perm_type
));
455 permissions_header
->SetMultiLine(true);
456 permissions_header
->SetHorizontalAlignment(gfx::ALIGN_LEFT
);
457 permissions_header
->SizeToFit(left_column_width
);
458 layout
->AddView(permissions_header
);
460 for (size_t i
= 0; i
< prompt_
->GetPermissionCount(perm_type
); ++i
) {
461 layout
->AddPaddingRow(0, views::kRelatedControlVerticalSpacing
);
462 layout
->StartRow(0, column_set_id
);
463 views::Label
* permission_label
=
464 new views::Label(prompt_
->GetPermission(i
, perm_type
));
466 permission_label
->SetMultiLine(true);
467 permission_label
->SetHorizontalAlignment(gfx::ALIGN_LEFT
);
468 permission_label
->SizeToFit(left_column_width
- kBulletWidth
);
469 layout
->AddView(new BulletedView(permission_label
));
471 // If we have more details to provide, show them in collapsed form.
472 if (!prompt_
->GetPermissionsDetails(i
, perm_type
).empty()) {
473 layout
->StartRow(0, column_set_id
);
474 PermissionDetails details
;
475 details
.push_back(PrepareForDisplay(
476 prompt_
->GetPermissionsDetails(i
, perm_type
), false));
477 ExpandableContainerView
* details_container
=
478 new ExpandableContainerView(this,
483 layout
->AddView(details_container
);
489 views::GridLayout
* ExtensionInstallDialogView::CreateLayout(
491 int left_column_width
,
492 int column_set_id
) const {
493 // This is basically views::GridLayout::CreatePanel, but without a top margin
494 // (we effectively get a top margin anyway from the empty dialog title).
495 views::GridLayout
* layout
= new views::GridLayout(parent
);
496 layout
->SetInsets(0, views::kButtonHEdgeMarginNew
, views::kPanelVertMargin
,
497 views::kButtonHEdgeMarginNew
);
498 parent
->SetLayoutManager(layout
);
500 views::ColumnSet
* column_set
= layout
->AddColumnSet(column_set_id
);
501 column_set
->AddColumn(views::GridLayout::LEADING
, views::GridLayout::LEADING
,
503 views::GridLayout::USE_PREF
,
506 column_set
->AddPaddingColumn(0, views::kPanelHorizMargin
);
507 column_set
->AddColumn(views::GridLayout::TRAILING
, views::GridLayout::LEADING
,
509 views::GridLayout::USE_PREF
,
513 layout
->StartRow(0, column_set_id
);
515 views::Label
* title
=
516 new views::Label(prompt_
->GetDialogTitle(),
517 ui::ResourceBundle::GetSharedInstance().GetFontList(
518 ui::ResourceBundle::MediumFont
));
519 title
->SetMultiLine(true);
520 title
->SetHorizontalAlignment(gfx::ALIGN_LEFT
);
521 title
->SizeToFit(left_column_width
);
522 layout
->AddView(title
);
524 // Scale down to icon size, but allow smaller icons (don't scale up).
525 const gfx::ImageSkia
* image
= prompt_
->icon().ToImageSkia();
526 gfx::Size
size(image
->width(), image
->height());
527 if (size
.width() > kIconSize
|| size
.height() > kIconSize
)
528 size
= gfx::Size(kIconSize
, kIconSize
);
529 views::ImageView
* icon
= new views::ImageView();
530 icon
->SetImageSize(size
);
531 icon
->SetImage(*image
);
532 icon
->SetHorizontalAlignment(views::ImageView::CENTER
);
533 icon
->SetVerticalAlignment(views::ImageView::CENTER
);
535 int icon_row_span
= 1;
536 if (prompt_
->has_webstore_data()) {
537 // Also span the rating, user_count and store_link rows.
539 // Note: Do not span the permissions here, there's a separator in between!
540 } else if (prompt_
->ShouldShowPermissions()) {
541 size_t permission_count
= prompt_
->GetPermissionCount(
542 ExtensionInstallPrompt::PermissionsType::ALL_PERMISSIONS
);
543 // Also span the permission header and each of the permission rows (all
544 // have a padding row above it). This also works for the 'no special
545 // permissions' case.
546 icon_row_span
= 3 + permission_count
* 2;
547 } else if (prompt_
->GetRetainedFileCount() ||
548 prompt_
->GetRetainedDeviceCount()) {
549 // Also span the permission header and the retained files container.
552 if (is_bundle_install()) {
553 // Also span the list of bundle items. One row per item, plus a padding row
555 int bundle_item_count
= prompt_
->bundle()->CountItemsWithState(
556 BundleInstaller::Item::STATE_PENDING
);
557 icon_row_span
+= bundle_item_count
+ 2;
559 layout
->AddView(icon
, 1, icon_row_span
);
564 void ExtensionInstallDialogView::ContentsChanged() {
568 int ExtensionInstallDialogView::GetDialogButtons() const {
569 int buttons
= prompt_
->GetDialogButtons();
570 // Simply having just an OK button is *not* supported. See comment on function
571 // GetDialogButtons in dialog_delegate.h for reasons.
572 DCHECK_GT(buttons
& ui::DIALOG_BUTTON_CANCEL
, 0);
576 base::string16
ExtensionInstallDialogView::GetDialogButtonLabel(
577 ui::DialogButton button
) const {
579 case ui::DIALOG_BUTTON_OK
:
580 return prompt_
->GetAcceptButtonLabel();
581 case ui::DIALOG_BUTTON_CANCEL
:
582 return prompt_
->GetAbortButtonLabel();
585 return base::string16();
589 int ExtensionInstallDialogView::GetDefaultDialogButton() const {
590 return ui::DIALOG_BUTTON_CANCEL
;
593 bool ExtensionInstallDialogView::Cancel() {
597 handled_result_
= true;
598 UpdateInstallResultHistogram(false);
600 sampling_event_
->CreateUserDecisionEvent(ExperienceSamplingEvent::kDeny
);
601 delegate_
->InstallUIAbort(true);
605 bool ExtensionInstallDialogView::Accept() {
606 DCHECK(!handled_result_
);
608 handled_result_
= true;
609 UpdateInstallResultHistogram(true);
611 sampling_event_
->CreateUserDecisionEvent(ExperienceSamplingEvent::kProceed
);
612 delegate_
->InstallUIProceed();
616 ui::ModalType
ExtensionInstallDialogView::GetModalType() const {
617 return ui::MODAL_TYPE_WINDOW
;
620 void ExtensionInstallDialogView::LinkClicked(views::Link
* source
,
622 GURL
store_url(extension_urls::GetWebstoreItemDetailURLPrefix() +
623 prompt_
->extension()->id());
624 OpenURLParams
params(
625 store_url
, Referrer(), NEW_FOREGROUND_TAB
,
626 ui::PAGE_TRANSITION_LINK
,
630 navigator_
->OpenURL(params
);
632 chrome::ScopedTabbedBrowserDisplayer
displayer(
633 profile_
, chrome::GetActiveDesktop());
634 displayer
.browser()->OpenURL(params
);
636 GetWidget()->Close();
639 void ExtensionInstallDialogView::Layout() {
640 scroll_view_
->SetBounds(0, 0, width(), height());
641 DialogDelegateView::Layout();
644 gfx::Size
ExtensionInstallDialogView::GetPreferredSize() const {
648 void ExtensionInstallDialogView::UpdateInstallResultHistogram(bool accepted
)
650 if (prompt_
->type() == ExtensionInstallPrompt::INSTALL_PROMPT
)
651 UMA_HISTOGRAM_BOOLEAN("Extensions.InstallPrompt.Accepted", accepted
);
654 // ExpandableContainerView::DetailsView ----------------------------------------
656 ExpandableContainerView::DetailsView::DetailsView(int horizontal_space
,
657 bool parent_bulleted
)
658 : layout_(new views::GridLayout(this)),
660 SetLayoutManager(layout_
);
661 views::ColumnSet
* column_set
= layout_
->AddColumnSet(0);
662 // If the parent is using bullets for its items, then a padding of one unit
663 // will make the child item (which has no bullet) look like a sibling of its
664 // parent. Therefore increase the indentation by one more unit to show that it
665 // is in fact a child item (with no missing bullet) and not a sibling.
667 views::kRelatedControlHorizontalSpacing
* (parent_bulleted
? 2 : 1);
668 column_set
->AddPaddingColumn(0, padding
);
669 column_set
->AddColumn(views::GridLayout::LEADING
,
670 views::GridLayout::LEADING
,
672 views::GridLayout::FIXED
,
673 horizontal_space
- padding
,
677 void ExpandableContainerView::DetailsView::AddDetail(
678 const base::string16
& detail
) {
679 layout_
->StartRowWithPadding(0, 0,
680 0, views::kRelatedControlSmallVerticalSpacing
);
681 views::Label
* detail_label
=
682 new views::Label(PrepareForDisplay(detail
, false));
683 detail_label
->SetMultiLine(true);
684 detail_label
->SetHorizontalAlignment(gfx::ALIGN_LEFT
);
685 layout_
->AddView(detail_label
);
688 gfx::Size
ExpandableContainerView::DetailsView::GetPreferredSize() const {
689 gfx::Size size
= views::View::GetPreferredSize();
690 return gfx::Size(size
.width(), size
.height() * state_
);
693 void ExpandableContainerView::DetailsView::AnimateToState(double state
) {
695 PreferredSizeChanged();
699 // ExpandableContainerView -----------------------------------------------------
701 ExpandableContainerView::ExpandableContainerView(
702 ExtensionInstallDialogView
* owner
,
703 const base::string16
& description
,
704 const PermissionDetails
& details
,
705 int horizontal_space
,
706 bool parent_bulleted
)
709 slide_animation_(this),
713 views::GridLayout
* layout
= new views::GridLayout(this);
714 SetLayoutManager(layout
);
715 int column_set_id
= 0;
716 views::ColumnSet
* column_set
= layout
->AddColumnSet(column_set_id
);
717 column_set
->AddColumn(views::GridLayout::LEADING
,
718 views::GridLayout::LEADING
,
720 views::GridLayout::USE_PREF
,
723 if (!description
.empty()) {
724 layout
->StartRow(0, column_set_id
);
726 views::Label
* description_label
= new views::Label(description
);
727 description_label
->SetMultiLine(true);
728 description_label
->SetHorizontalAlignment(gfx::ALIGN_LEFT
);
729 description_label
->SizeToFit(horizontal_space
);
730 layout
->AddView(new BulletedView(description_label
));
736 details_view_
= new DetailsView(horizontal_space
, parent_bulleted
);
738 layout
->StartRow(0, column_set_id
);
739 layout
->AddView(details_view_
);
741 for (size_t i
= 0; i
< details
.size(); ++i
)
742 details_view_
->AddDetail(details
[i
]);
744 views::Link
* link
= new views::Link(
745 l10n_util::GetStringUTF16(IDS_EXTENSIONS_SHOW_DETAILS
));
747 // Make sure the link width column is as wide as needed for both Show and
748 // Hide details, so that the arrow doesn't shift horizontally when we
751 views::kRelatedControlHorizontalSpacing
+
752 std::max(gfx::GetStringWidth(
753 l10n_util::GetStringUTF16(IDS_EXTENSIONS_HIDE_DETAILS
),
756 l10n_util::GetStringUTF16(IDS_EXTENSIONS_SHOW_DETAILS
),
759 column_set
= layout
->AddColumnSet(++column_set_id
);
760 // Padding to the left of the More Details column. If the parent is using
761 // bullets for its items, then a padding of one unit will make the child
762 // item (which has no bullet) look like a sibling of its parent. Therefore
763 // increase the indentation by one more unit to show that it is in fact a
764 // child item (with no missing bullet) and not a sibling.
765 column_set
->AddPaddingColumn(
766 0, views::kRelatedControlHorizontalSpacing
* (parent_bulleted
? 2 : 1));
767 // The More Details column.
768 column_set
->AddColumn(views::GridLayout::LEADING
,
769 views::GridLayout::LEADING
,
771 views::GridLayout::FIXED
,
774 // The Up/Down arrow column.
775 column_set
->AddColumn(views::GridLayout::LEADING
,
776 views::GridLayout::LEADING
,
778 views::GridLayout::USE_PREF
,
782 // Add the More Details link.
783 layout
->StartRow(0, column_set_id
);
784 more_details_
= link
;
785 more_details_
->set_listener(this);
786 more_details_
->SetHorizontalAlignment(gfx::ALIGN_LEFT
);
787 layout
->AddView(more_details_
);
789 // Add the arrow after the More Details link.
790 ui::ResourceBundle
& rb
= ui::ResourceBundle::GetSharedInstance();
791 arrow_toggle_
= new views::ImageButton(this);
792 arrow_toggle_
->SetImage(views::Button::STATE_NORMAL
,
793 rb
.GetImageSkiaNamed(IDR_DOWN_ARROW
));
794 layout
->AddView(arrow_toggle_
);
797 ExpandableContainerView::~ExpandableContainerView() {
800 void ExpandableContainerView::ButtonPressed(
801 views::Button
* sender
, const ui::Event
& event
) {
805 void ExpandableContainerView::LinkClicked(
806 views::Link
* source
, int event_flags
) {
810 void ExpandableContainerView::AnimationProgressed(
811 const gfx::Animation
* animation
) {
812 DCHECK_EQ(&slide_animation_
, animation
);
814 details_view_
->AnimateToState(animation
->GetCurrentValue());
817 void ExpandableContainerView::AnimationEnded(const gfx::Animation
* animation
) {
819 if (animation
->GetCurrentValue() != 0.0) {
820 arrow_toggle_
->SetImage(
821 views::Button::STATE_NORMAL
,
822 ui::ResourceBundle::GetSharedInstance().GetImageSkiaNamed(
825 arrow_toggle_
->SetImage(
826 views::Button::STATE_NORMAL
,
827 ui::ResourceBundle::GetSharedInstance().GetImageSkiaNamed(
832 more_details_
->SetText(expanded_
?
833 l10n_util::GetStringUTF16(IDS_EXTENSIONS_HIDE_DETAILS
) :
834 l10n_util::GetStringUTF16(IDS_EXTENSIONS_SHOW_DETAILS
));
838 void ExpandableContainerView::ChildPreferredSizeChanged(views::View
* child
) {
839 owner_
->ContentsChanged();
842 void ExpandableContainerView::ToggleDetailLevel() {
843 expanded_
= !expanded_
;
845 if (slide_animation_
.IsShowing())
846 slide_animation_
.Hide();
848 slide_animation_
.Show();
852 ExtensionInstallPrompt::ShowDialogCallback
853 ExtensionInstallPrompt::GetDefaultShowDialogCallback() {
854 return base::Bind(&ShowExtensionInstallDialogImpl
);