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.
7 #include "base/basictypes.h"
8 #include "base/command_line.h"
9 #include "base/compiler_specific.h"
10 #include "base/i18n/rtl.h"
11 #include "base/metrics/histogram.h"
12 #include "base/strings/string_util.h"
13 #include "base/strings/utf_string_conversions.h"
14 #include "chrome/browser/extensions/bundle_installer.h"
15 #include "chrome/browser/extensions/extension_install_prompt.h"
16 #include "chrome/browser/extensions/extension_install_prompt_experiment.h"
17 #include "chrome/browser/profiles/profile.h"
18 #include "chrome/browser/ui/views/constrained_window_views.h"
19 #include "chrome/common/chrome_switches.h"
20 #include "chrome/common/extensions/extension_constants.h"
21 #include "chrome/installer/util/browser_distribution.h"
22 #include "content/public/browser/page_navigator.h"
23 #include "content/public/browser/web_contents.h"
24 #include "extensions/common/extension.h"
25 #include "grit/chromium_strings.h"
26 #include "grit/generated_resources.h"
27 #include "grit/google_chrome_strings.h"
28 #include "grit/theme_resources.h"
29 #include "ui/base/l10n/l10n_util.h"
30 #include "ui/base/resource/resource_bundle.h"
31 #include "ui/gfx/animation/animation_delegate.h"
32 #include "ui/gfx/animation/slide_animation.h"
33 #include "ui/gfx/text_utils.h"
34 #include "ui/gfx/transform.h"
35 #include "ui/views/background.h"
36 #include "ui/views/border.h"
37 #include "ui/views/controls/button/checkbox.h"
38 #include "ui/views/controls/button/image_button.h"
39 #include "ui/views/controls/button/label_button.h"
40 #include "ui/views/controls/image_view.h"
41 #include "ui/views/controls/label.h"
42 #include "ui/views/controls/link.h"
43 #include "ui/views/controls/link_listener.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/view.h"
50 #include "ui/views/widget/widget.h"
51 #include "ui/views/window/dialog_client_view.h"
52 #include "ui/views/window/dialog_delegate.h"
54 using content::OpenURLParams
;
55 using content::Referrer
;
56 using extensions::BundleInstaller
;
60 // Size of extension icon in top left of dialog.
61 const int kIconSize
= 69;
63 // We offset the icon a little bit from the right edge of the dialog, to make it
64 // align with the button below it.
65 const int kIconOffset
= 16;
67 // The dialog will resize based on its content, but this sets a maximum height
68 // before overflowing a scrollbar.
69 const int kDialogMaxHeight
= 300;
71 // Width of the left column of the dialog when the extension requests
73 const int kPermissionsLeftColumnWidth
= 250;
75 // Width of the left column of the dialog when the extension requests no
77 const int kNoPermissionsLeftColumnWidth
= 200;
79 // Width of the left column for bundle install prompts. There's only one column
80 // in this case, so make it wider than normal.
81 const int kBundleLeftColumnWidth
= 300;
83 // Width of the left column for external install prompts. The text is long in
84 // this case, so make it wider than normal.
85 const int kExternalInstallLeftColumnWidth
= 350;
87 // Lighter color for labels.
88 const SkColor kLighterLabelColor
= SkColorSetRGB(0x99, 0x99, 0x99);
90 // Represents an action on a clickable link created by the install prompt
91 // experiment. This is used to group the actions in UMA histograms named
92 // Extensions.InstallPromptExperiment.ShowDetails and
93 // Extensions.InstallPromptExperiment.ShowPermissions.
94 enum ExperimentLinkAction
{
101 typedef std::vector
<base::string16
> PermissionDetails
;
102 class ExpandableContainerView
;
104 void AddResourceIcon(const gfx::ImageSkia
* skia_image
, void* data
) {
105 views::View
* parent
= static_cast<views::View
*>(data
);
106 views::ImageView
* image_view
= new views::ImageView();
107 image_view
->SetImage(*skia_image
);
108 parent
->AddChildView(image_view
);
111 // Creates a string for displaying |message| to the user. If it has to look
112 // like a entry in a bullet point list, one is added.
113 base::string16
PrepareForDisplay(const base::string16
& message
,
115 return bullet_point
? l10n_util::GetStringFUTF16(
116 IDS_EXTENSION_PERMISSION_LINE
,
120 // A custom scrollable view implementation for the dialog.
121 class CustomScrollableView
: public views::View
{
123 CustomScrollableView();
124 virtual ~CustomScrollableView();
127 virtual void Layout() OVERRIDE
;
129 DISALLOW_COPY_AND_ASSIGN(CustomScrollableView
);
132 // Implements the extension installation dialog for TOOLKIT_VIEWS.
133 class ExtensionInstallDialogView
: public views::DialogDelegateView
,
134 public views::LinkListener
,
135 public views::ButtonListener
{
137 ExtensionInstallDialogView(content::PageNavigator
* navigator
,
138 ExtensionInstallPrompt::Delegate
* delegate
,
139 const ExtensionInstallPrompt::Prompt
& prompt
);
140 virtual ~ExtensionInstallDialogView();
142 // Called when one of the child elements has expanded/collapsed.
143 void ContentsChanged();
146 // views::DialogDelegateView:
147 virtual int GetDialogButtons() const OVERRIDE
;
148 virtual base::string16
GetDialogButtonLabel(
149 ui::DialogButton button
) const OVERRIDE
;
150 virtual int GetDefaultDialogButton() const OVERRIDE
;
151 virtual bool Cancel() OVERRIDE
;
152 virtual bool Accept() OVERRIDE
;
153 virtual ui::ModalType
GetModalType() const OVERRIDE
;
154 virtual base::string16
GetWindowTitle() const OVERRIDE
;
155 virtual void Layout() OVERRIDE
;
156 virtual gfx::Size
GetPreferredSize() const OVERRIDE
;
157 virtual void ViewHierarchyChanged(
158 const ViewHierarchyChangedDetails
& details
) OVERRIDE
;
160 // views::LinkListener:
161 virtual void LinkClicked(views::Link
* source
, int event_flags
) OVERRIDE
;
163 // views::ButtonListener:
164 virtual void ButtonPressed(views::Button
* sender
,
165 const ui::Event
& event
) OVERRIDE
;
167 // Experimental: Toggles inline permission explanations with an animation.
168 void ToggleInlineExplanations();
170 // Creates a layout consisting of dialog header, extension name and icon.
171 views::GridLayout
* CreateLayout(
173 int left_column_width
,
175 bool single_detail_row
) const;
177 bool is_inline_install() const {
178 return prompt_
.type() == ExtensionInstallPrompt::INLINE_INSTALL_PROMPT
;
181 bool is_bundle_install() const {
182 return prompt_
.type() == ExtensionInstallPrompt::BUNDLE_INSTALL_PROMPT
;
185 bool is_external_install() const {
186 return prompt_
.type() == ExtensionInstallPrompt::EXTERNAL_INSTALL_PROMPT
;
189 // Updates the histogram that holds installation accepted/aborted data.
190 void UpdateInstallResultHistogram(bool accepted
) const;
192 // Updates the histogram that holds data about whether "Show details" or
193 // "Show permissions" links were shown and/or clicked.
194 void UpdateLinkActionHistogram(int action_type
) const;
196 content::PageNavigator
* navigator_
;
197 ExtensionInstallPrompt::Delegate
* delegate_
;
198 const ExtensionInstallPrompt::Prompt
& prompt_
;
200 // The scroll view containing all the details for the dialog (including all
201 // collapsible/expandable sections).
202 views::ScrollView
* scroll_view_
;
204 // The container view for the scroll view.
205 CustomScrollableView
* scrollable_
;
207 // The container for the simpler view with only the dialog header and the
208 // extension icon. Used for the experiment where the permissions are
209 // initially hidden when the dialog shows.
210 CustomScrollableView
* scrollable_header_only_
;
212 // The preferred size of the dialog.
213 gfx::Size dialog_size_
;
215 // Experimental: "Show details" link to expand inline explanations and reveal
217 views::Link
* show_details_link_
;
219 // Experimental: Label for showing information about the checkboxes.
220 views::Label
* checkbox_info_label_
;
222 // Experimental: Contains pointers to inline explanation views.
223 typedef std::vector
<ExpandableContainerView
*> InlineExplanations
;
224 InlineExplanations inline_explanations_
;
226 // Experimental: Number of unchecked checkboxes in the permission list.
227 // If this becomes zero, the accept button is enabled, otherwise disabled.
228 int unchecked_boxes_
;
230 DISALLOW_COPY_AND_ASSIGN(ExtensionInstallDialogView
);
233 // A simple view that prepends a view with a bullet with the help of a grid
235 class BulletedView
: public views::View
{
237 explicit BulletedView(views::View
* view
);
239 DISALLOW_COPY_AND_ASSIGN(BulletedView
);
242 BulletedView::BulletedView(views::View
* view
) {
243 views::GridLayout
* layout
= new views::GridLayout(this);
244 SetLayoutManager(layout
);
245 views::ColumnSet
* column_set
= layout
->AddColumnSet(0);
246 column_set
->AddColumn(views::GridLayout::LEADING
,
247 views::GridLayout::LEADING
,
249 views::GridLayout::USE_PREF
,
252 column_set
->AddColumn(views::GridLayout::LEADING
,
253 views::GridLayout::LEADING
,
255 views::GridLayout::USE_PREF
,
258 layout
->StartRow(0, 0);
259 layout
->AddView(new views::Label(PrepareForDisplay(base::string16(), true)));
260 layout
->AddView(view
);
263 // A simple view that prepends a view with a checkbox with the help of a grid
264 // layout. Used for the permission experiment.
265 // TODO(meacer): Remove once the experiment is completed.
266 class CheckboxedView
: public views::View
{
268 CheckboxedView(views::View
* view
, views::ButtonListener
* listener
);
270 DISALLOW_COPY_AND_ASSIGN(CheckboxedView
);
273 CheckboxedView::CheckboxedView(views::View
* view
,
274 views::ButtonListener
* listener
) {
275 views::GridLayout
* layout
= new views::GridLayout(this);
276 SetLayoutManager(layout
);
277 views::ColumnSet
* column_set
= layout
->AddColumnSet(0);
278 column_set
->AddColumn(views::GridLayout::LEADING
,
279 views::GridLayout::LEADING
,
281 views::GridLayout::USE_PREF
,
284 column_set
->AddColumn(views::GridLayout::LEADING
,
285 views::GridLayout::LEADING
,
287 views::GridLayout::USE_PREF
,
290 layout
->StartRow(0, 0);
291 views::Checkbox
* checkbox
= new views::Checkbox(base::string16());
292 checkbox
->set_listener(listener
);
293 // Alignment needs to be explicitly set again here, otherwise the views are
294 // not vertically centered.
295 layout
->AddView(checkbox
, 1, 1,
296 views::GridLayout::LEADING
, views::GridLayout::CENTER
);
297 layout
->AddView(view
, 1, 1,
298 views::GridLayout::LEADING
, views::GridLayout::CENTER
);
301 // A view to display text with an expandable details section.
302 class ExpandableContainerView
: public views::View
,
303 public views::ButtonListener
,
304 public views::LinkListener
,
305 public gfx::AnimationDelegate
{
307 ExpandableContainerView(ExtensionInstallDialogView
* owner
,
308 const base::string16
& description
,
309 const PermissionDetails
& details
,
310 int horizontal_space
,
311 bool parent_bulleted
,
312 bool show_expand_link
,
313 bool lighter_color_details
);
314 virtual ~ExpandableContainerView();
317 virtual void ChildPreferredSizeChanged(views::View
* child
) OVERRIDE
;
319 // views::ButtonListener:
320 virtual void ButtonPressed(views::Button
* sender
,
321 const ui::Event
& event
) OVERRIDE
;
323 // views::LinkListener:
324 virtual void LinkClicked(views::Link
* source
, int event_flags
) OVERRIDE
;
326 // gfx::AnimationDelegate:
327 virtual void AnimationProgressed(const gfx::Animation
* animation
) OVERRIDE
;
328 virtual void AnimationEnded(const gfx::Animation
* animation
) OVERRIDE
;
330 // Expand/Collapse the detail section for this ExpandableContainerView.
331 void ToggleDetailLevel();
333 // Expand the detail section without any animation.
334 // TODO(meacer): Remove once the experiment is completed.
335 void ExpandWithoutAnimation();
338 // A view which displays all the details of an IssueAdviceInfoEntry.
339 class DetailsView
: public views::View
{
341 explicit DetailsView(int horizontal_space
, bool parent_bulleted
,
343 virtual ~DetailsView() {}
346 virtual gfx::Size
GetPreferredSize() const OVERRIDE
;
348 void AddDetail(const base::string16
& detail
);
350 // Animates this to be a height proportional to |state|.
351 void AnimateToState(double state
);
354 views::GridLayout
* layout_
;
357 // Whether the detail text should be shown with a lighter color.
360 DISALLOW_COPY_AND_ASSIGN(DetailsView
);
363 // The dialog that owns |this|. It's also an ancestor in the View hierarchy.
364 ExtensionInstallDialogView
* owner_
;
366 // A view for showing |issue_advice.details|.
367 DetailsView
* details_view_
;
369 // The 'more details' link shown under the heading (changes to 'hide details'
370 // when the details section is expanded).
371 views::Link
* more_details_
;
373 gfx::SlideAnimation slide_animation_
;
375 // The up/down arrow next to the 'more detail' link (points up/down depending
376 // on whether the details section is expanded).
377 views::ImageButton
* arrow_toggle_
;
379 // Whether the details section is expanded.
382 DISALLOW_COPY_AND_ASSIGN(ExpandableContainerView
);
385 void ShowExtensionInstallDialogImpl(
386 const ExtensionInstallPrompt::ShowParams
& show_params
,
387 ExtensionInstallPrompt::Delegate
* delegate
,
388 const ExtensionInstallPrompt::Prompt
& prompt
) {
389 DCHECK_CURRENTLY_ON(content::BrowserThread::UI
);
390 CreateBrowserModalDialogViews(
391 new ExtensionInstallDialogView(show_params
.navigator
, delegate
, prompt
),
392 show_params
.parent_window
)->Show();
397 CustomScrollableView::CustomScrollableView() {}
398 CustomScrollableView::~CustomScrollableView() {}
400 void CustomScrollableView::Layout() {
401 SetBounds(x(), y(), width(), GetHeightForWidth(width()));
402 views::View::Layout();
405 ExtensionInstallDialogView::ExtensionInstallDialogView(
406 content::PageNavigator
* navigator
,
407 ExtensionInstallPrompt::Delegate
* delegate
,
408 const ExtensionInstallPrompt::Prompt
& prompt
)
409 : navigator_(navigator
),
414 scrollable_header_only_(NULL
),
415 show_details_link_(NULL
),
416 checkbox_info_label_(NULL
),
417 unchecked_boxes_(0) {
418 // Possible grid layouts without ExtensionPermissionDialog experiment:
420 // w/ permissions no permissions
421 // +--------------------+------+ +--------------+------+
422 // | heading | icon | | heading | icon |
423 // +--------------------| | +--------------| |
424 // | rating | | | rating | |
425 // +--------------------| | +--------------+ |
426 // | user_count | | | user_count | |
427 // +--------------------| | +--------------| |
428 // | store_link | | | store_link | |
429 // +--------------------+------+ +--------------+------+
431 // +--------------------+------+
432 // | permissions_header | |
433 // +--------------------+------+
435 // +--------------------+------+
437 // +--------------------+------+
440 // w/ permissions XOR oauth issues no permissions
441 // +--------------------+------+ +--------------+------+
442 // | heading | icon | | heading | icon |
443 // +--------------------| | +--------------+------+
444 // | permissions_header | |
445 // +--------------------| |
447 // +--------------------| |
449 // +--------------------+------+
451 // w/ permissions AND oauth issues
452 // +--------------------+------+
453 // | heading | icon |
454 // +--------------------| |
455 // | permissions_header | |
456 // +--------------------| |
458 // +--------------------| |
460 // +--------------------+------+
462 // +---------------------------+
464 // +---------------------------+
466 // +---------------------------+
468 // If the ExtensionPermissionDialog is on, the layout is modified depending
469 // on the experiment group. For text only experiment, a footer is added at the
470 // bottom of the layouts. For others, inline details are added below some of
473 // Regular install w/ permissions and footer (experiment):
474 // +--------------------+------+
475 // | heading | icon |
476 // +--------------------| |
477 // | permissions_header | |
478 // +--------------------| |
480 // +--------------------| |
482 // +--------------------+------+
484 // +--------------------+------+
486 // Regular install w/ permissions and inline explanations (experiment):
487 // +--------------------+------+
488 // | heading | icon |
489 // +--------------------| |
490 // | permissions_header | |
491 // +--------------------| |
493 // +--------------------| |
494 // | explanation1 | |
495 // +--------------------| |
497 // +--------------------| |
498 // | explanation2 | |
499 // +--------------------+------+
501 // Regular install w/ permissions and inline explanations (experiment):
502 // +--------------------+------+
503 // | heading | icon |
504 // +--------------------| |
505 // | permissions_header | |
506 // +--------------------| |
507 // |checkbox|permission1| |
508 // +--------------------| |
509 // |checkbox|permission2| |
510 // +--------------------+------+
512 // Additionally, links or informational text is added to non-client areas of
513 // the dialog depending on the experiment group.
515 int left_column_width
=
516 (prompt
.ShouldShowPermissions() + prompt
.GetOAuthIssueCount() +
517 prompt
.GetRetainedFileCount()) > 0 ?
518 kPermissionsLeftColumnWidth
: kNoPermissionsLeftColumnWidth
;
519 if (is_bundle_install())
520 left_column_width
= kBundleLeftColumnWidth
;
521 if (is_external_install())
522 left_column_width
= kExternalInstallLeftColumnWidth
;
524 scroll_view_
= new views::ScrollView();
525 scroll_view_
->set_hide_horizontal_scrollbar(true);
526 AddChildView(scroll_view_
);
528 int column_set_id
= 0;
529 // Create the full scrollable view which will contain all the information
530 // including the permissions.
531 scrollable_
= new CustomScrollableView();
532 views::GridLayout
* layout
= CreateLayout(
533 scrollable_
, left_column_width
, column_set_id
, false);
534 ui::ResourceBundle
& rb
= ui::ResourceBundle::GetSharedInstance();
536 if (prompt
.ShouldShowPermissions() &&
537 prompt
.experiment()->should_show_expandable_permission_list()) {
538 // If the experiment should hide the permission list initially, create a
539 // simple layout that contains only the header, extension name and icon.
540 scrollable_header_only_
= new CustomScrollableView();
541 CreateLayout(scrollable_header_only_
, left_column_width
,
542 column_set_id
, true);
543 scroll_view_
->SetContents(scrollable_header_only_
);
545 scroll_view_
->SetContents(scrollable_
);
548 int dialog_width
= left_column_width
+ 2 * views::kPanelHorizMargin
;
549 if (!is_bundle_install())
550 dialog_width
+= views::kPanelHorizMargin
+ kIconSize
+ kIconOffset
;
552 // Widen the dialog for experiment with checkboxes so that the information
553 // label fits the area to the left of the buttons.
554 if (prompt
.experiment()->show_checkboxes())
555 dialog_width
+= 4 * views::kPanelHorizMargin
;
557 if (prompt
.has_webstore_data()) {
558 layout
->StartRow(0, column_set_id
);
559 views::View
* rating
= new views::View();
560 rating
->SetLayoutManager(new views::BoxLayout(
561 views::BoxLayout::kHorizontal
, 0, 0, 0));
562 layout
->AddView(rating
);
563 prompt
.AppendRatingStars(AddResourceIcon
, rating
);
565 const gfx::FontList
& small_font_list
=
566 rb
.GetFontList(ui::ResourceBundle::SmallFont
);
567 views::Label
* rating_count
=
568 new views::Label(prompt
.GetRatingCount(), small_font_list
);
569 // Add some space between the stars and the rating count.
570 rating_count
->SetBorder(views::Border::CreateEmptyBorder(0, 2, 0, 0));
571 rating
->AddChildView(rating_count
);
573 layout
->StartRow(0, column_set_id
);
574 views::Label
* user_count
=
575 new views::Label(prompt
.GetUserCount(), small_font_list
);
576 user_count
->SetAutoColorReadabilityEnabled(false);
577 user_count
->SetEnabledColor(SK_ColorGRAY
);
578 layout
->AddView(user_count
);
580 layout
->StartRow(0, column_set_id
);
581 views::Link
* store_link
= new views::Link(
582 l10n_util::GetStringUTF16(IDS_EXTENSION_PROMPT_STORE_LINK
));
583 store_link
->SetFontList(small_font_list
);
584 store_link
->set_listener(this);
585 layout
->AddView(store_link
);
588 if (is_bundle_install()) {
589 BundleInstaller::ItemList items
= prompt
.bundle()->GetItemsWithState(
590 BundleInstaller::Item::STATE_PENDING
);
591 for (size_t i
= 0; i
< items
.size(); ++i
) {
592 base::string16 extension_name
=
593 base::UTF8ToUTF16(items
[i
].localized_name
);
594 base::i18n::AdjustStringForLocaleDirection(&extension_name
);
595 layout
->AddPaddingRow(0, views::kRelatedControlVerticalSpacing
);
596 layout
->StartRow(0, column_set_id
);
597 views::Label
* extension_label
= new views::Label(
598 PrepareForDisplay(extension_name
, true));
599 extension_label
->SetMultiLine(true);
600 extension_label
->SetHorizontalAlignment(gfx::ALIGN_LEFT
);
601 extension_label
->SizeToFit(left_column_width
);
602 layout
->AddView(extension_label
);
606 if (prompt
.ShouldShowPermissions()) {
607 layout
->AddPaddingRow(0, views::kRelatedControlVerticalSpacing
);
609 if (prompt
.GetPermissionCount() > 0) {
610 if (is_inline_install()) {
611 layout
->StartRow(0, column_set_id
);
612 layout
->AddView(new views::Separator(views::Separator::HORIZONTAL
),
613 3, 1, views::GridLayout::FILL
, views::GridLayout::FILL
);
614 layout
->AddPaddingRow(0, views::kRelatedControlVerticalSpacing
);
617 layout
->StartRow(0, column_set_id
);
618 views::Label
* permissions_header
= NULL
;
619 if (is_bundle_install()) {
620 // We need to pass the FontList in the constructor, rather than calling
621 // SetFontList later, because otherwise SizeToFit mis-judges the width
623 permissions_header
= new views::Label(
624 prompt
.GetPermissionsHeading(),
625 rb
.GetFontList(ui::ResourceBundle::MediumFont
));
627 permissions_header
= new views::Label(prompt
.GetPermissionsHeading());
629 permissions_header
->SetMultiLine(true);
630 permissions_header
->SetHorizontalAlignment(gfx::ALIGN_LEFT
);
631 permissions_header
->SizeToFit(left_column_width
);
632 layout
->AddView(permissions_header
);
634 for (size_t i
= 0; i
< prompt
.GetPermissionCount(); ++i
) {
635 layout
->AddPaddingRow(0, views::kRelatedControlVerticalSpacing
);
636 layout
->StartRow(0, column_set_id
);
637 views::Label
* permission_label
=
638 new views::Label(prompt
.GetPermission(i
));
640 const SkColor kTextHighlight
= SK_ColorRED
;
641 const SkColor kBackgroundHighlight
= SkColorSetRGB(0xFB, 0xF7, 0xA3);
642 if (prompt
.experiment()->ShouldHighlightText(
643 prompt
.GetPermission(i
))) {
644 permission_label
->SetAutoColorReadabilityEnabled(false);
645 permission_label
->SetEnabledColor(kTextHighlight
);
646 } else if (prompt
.experiment()->ShouldHighlightBackground(
647 prompt
.GetPermission(i
))) {
648 permission_label
->SetLineHeight(18);
649 permission_label
->set_background(
650 views::Background::CreateSolidBackground(kBackgroundHighlight
));
653 permission_label
->SetMultiLine(true);
654 permission_label
->SetHorizontalAlignment(gfx::ALIGN_LEFT
);
655 permission_label
->SizeToFit(left_column_width
);
657 if (prompt
.experiment()->show_checkboxes()) {
658 layout
->AddView(new CheckboxedView(permission_label
, this));
661 layout
->AddView(new BulletedView(permission_label
));
663 // If we have more details to provide, show them in collapsed form.
664 if (!prompt
.GetPermissionsDetails(i
).empty()) {
665 layout
->StartRow(0, column_set_id
);
666 PermissionDetails details
;
668 PrepareForDisplay(prompt
.GetPermissionsDetails(i
), false));
669 ExpandableContainerView
* details_container
=
670 new ExpandableContainerView(
671 this, base::string16(), details
, left_column_width
,
673 layout
->AddView(details_container
);
676 if (prompt
.experiment()->should_show_inline_explanations()) {
677 base::string16 explanation
=
678 prompt
.experiment()->GetInlineExplanation(
679 prompt
.GetPermission(i
));
680 if (!explanation
.empty()) {
681 PermissionDetails details
;
682 details
.push_back(explanation
);
683 ExpandableContainerView
* container
=
684 new ExpandableContainerView(this, base::string16(), details
,
687 // Inline explanations are expanded by default if there is
688 // no "Show details" link.
689 if (!prompt
.experiment()->show_details_link())
690 container
->ExpandWithoutAnimation();
691 layout
->StartRow(0, column_set_id
);
692 layout
->AddView(container
);
693 inline_explanations_
.push_back(container
);
698 layout
->AddPaddingRow(0, views::kRelatedControlVerticalSpacing
);
699 layout
->StartRow(0, column_set_id
);
700 views::Label
* permission_label
= new views::Label(
701 l10n_util::GetStringUTF16(IDS_EXTENSION_NO_SPECIAL_PERMISSIONS
));
702 permission_label
->SetMultiLine(true);
703 permission_label
->SetHorizontalAlignment(gfx::ALIGN_LEFT
);
704 permission_label
->SizeToFit(left_column_width
);
705 layout
->AddView(permission_label
);
709 if (prompt
.GetOAuthIssueCount()) {
710 // Slide in under the permissions, if there are any. If there are
711 // permissions, the OAuth prompt stretches all the way to the right of the
712 // dialog. If there are no permissions, the OAuth prompt just takes up the
714 int space_for_oauth
= left_column_width
;
715 if (prompt
.GetPermissionCount()) {
716 space_for_oauth
+= kIconSize
;
717 views::ColumnSet
* column_set
= layout
->AddColumnSet(++column_set_id
);
718 column_set
->AddColumn(views::GridLayout::FILL
,
719 views::GridLayout::FILL
,
721 views::GridLayout::USE_PREF
,
726 layout
->StartRowWithPadding(0, column_set_id
,
727 0, views::kRelatedControlVerticalSpacing
);
728 views::Label
* oauth_header
= new views::Label(prompt
.GetOAuthHeading());
729 oauth_header
->SetMultiLine(true);
730 oauth_header
->SetHorizontalAlignment(gfx::ALIGN_LEFT
);
731 oauth_header
->SizeToFit(left_column_width
);
732 layout
->AddView(oauth_header
);
734 for (size_t i
= 0; i
< prompt
.GetOAuthIssueCount(); ++i
) {
735 layout
->StartRowWithPadding(
737 0, views::kRelatedControlVerticalSpacing
);
739 PermissionDetails details
;
740 const IssueAdviceInfoEntry
& entry
= prompt
.GetOAuthIssue(i
);
741 for (size_t x
= 0; x
< entry
.details
.size(); ++x
)
742 details
.push_back(entry
.details
[x
]);
743 ExpandableContainerView
* issue_advice_view
=
744 new ExpandableContainerView(
745 this, entry
.description
, details
, space_for_oauth
,
747 layout
->AddView(issue_advice_view
);
750 if (prompt
.GetRetainedFileCount()) {
751 // Slide in under the permissions or OAuth, if there are any. If there are
752 // either, the retained files prompt stretches all the way to the right of
753 // the dialog. If there are no permissions or OAuth, the retained files
754 // prompt just takes up the left column.
755 int space_for_files
= left_column_width
;
756 if (prompt
.GetPermissionCount() || prompt
.GetOAuthIssueCount()) {
757 space_for_files
+= kIconSize
;
758 views::ColumnSet
* column_set
= layout
->AddColumnSet(++column_set_id
);
759 column_set
->AddColumn(views::GridLayout::FILL
,
760 views::GridLayout::FILL
,
762 views::GridLayout::USE_PREF
,
767 layout
->AddPaddingRow(0, views::kRelatedControlVerticalSpacing
);
769 layout
->StartRow(0, column_set_id
);
770 views::Label
* retained_files_header
= NULL
;
771 retained_files_header
=
772 new views::Label(prompt
.GetRetainedFilesHeading());
773 retained_files_header
->SetMultiLine(true);
774 retained_files_header
->SetHorizontalAlignment(gfx::ALIGN_LEFT
);
775 retained_files_header
->SizeToFit(space_for_files
);
776 layout
->AddView(retained_files_header
);
778 layout
->StartRow(0, column_set_id
);
779 PermissionDetails details
;
780 for (size_t i
= 0; i
< prompt
.GetRetainedFileCount(); ++i
)
781 details
.push_back(prompt
.GetRetainedFile(i
));
782 ExpandableContainerView
* issue_advice_view
=
783 new ExpandableContainerView(
784 this, base::string16(), details
, space_for_files
,
786 layout
->AddView(issue_advice_view
);
789 DCHECK(prompt
.type() >= 0);
790 UMA_HISTOGRAM_ENUMERATION("Extensions.InstallPrompt.Type",
792 ExtensionInstallPrompt::NUM_PROMPT_TYPES
);
794 if (prompt
.ShouldShowPermissions()) {
795 if (prompt
.ShouldShowExplanationText()) {
796 views::ColumnSet
* column_set
= layout
->AddColumnSet(++column_set_id
);
797 column_set
->AddColumn(views::GridLayout::LEADING
,
798 views::GridLayout::FILL
,
800 views::GridLayout::USE_PREF
,
803 // Add two rows of space so that the text stands out.
804 layout
->AddPaddingRow(0, 2 * views::kRelatedControlVerticalSpacing
);
806 layout
->StartRow(0, column_set_id
);
807 views::Label
* explanation
= new views::Label(
808 prompt
.experiment()->GetExplanationText());
809 explanation
->SetMultiLine(true);
810 explanation
->SetHorizontalAlignment(gfx::ALIGN_LEFT
);
811 explanation
->SizeToFit(left_column_width
+ kIconSize
);
812 layout
->AddView(explanation
);
815 if (prompt
.experiment()->should_show_expandable_permission_list() ||
816 (prompt
.experiment()->show_details_link() &&
817 prompt
.experiment()->should_show_inline_explanations() &&
818 !inline_explanations_
.empty())) {
819 // Don't show the "Show details" link if there are OAuth issues or
820 // retained files. These have their own "Show details" links and having
821 // multiple levels of links is confusing.
822 if (prompt
.GetOAuthIssueCount() + prompt
.GetRetainedFileCount() == 0) {
824 prompt
.experiment()->should_show_expandable_permission_list() ?
825 IDS_EXTENSION_PROMPT_EXPERIMENT_SHOW_PERMISSIONS
:
826 IDS_EXTENSION_PROMPT_EXPERIMENT_SHOW_DETAILS
;
827 show_details_link_
= new views::Link(
828 l10n_util::GetStringUTF16(text_id
));
829 show_details_link_
->SetHorizontalAlignment(gfx::ALIGN_LEFT
);
830 show_details_link_
->set_listener(this);
831 UpdateLinkActionHistogram(LINK_SHOWN
);
833 UpdateLinkActionHistogram(LINK_NOT_SHOWN
);
837 if (prompt
.experiment()->show_checkboxes()) {
838 checkbox_info_label_
= new views::Label(
839 l10n_util::GetStringUTF16(
840 IDS_EXTENSION_PROMPT_EXPERIMENT_CHECKBOX_INFO
));
841 checkbox_info_label_
->SetMultiLine(true);
842 checkbox_info_label_
->SetHorizontalAlignment(gfx::ALIGN_LEFT
);
843 checkbox_info_label_
->SetAutoColorReadabilityEnabled(false);
844 checkbox_info_label_
->SetEnabledColor(kLighterLabelColor
);
848 gfx::Size scrollable_size
= scrollable_
->GetPreferredSize();
849 scrollable_
->SetBoundsRect(gfx::Rect(scrollable_size
));
850 dialog_size_
= gfx::Size(
852 std::min(scrollable_size
.height(), kDialogMaxHeight
));
854 if (scrollable_header_only_
) {
855 gfx::Size header_only_size
= scrollable_header_only_
->GetPreferredSize();
856 scrollable_header_only_
->SetBoundsRect(gfx::Rect(header_only_size
));
857 dialog_size_
= gfx::Size(
858 dialog_width
, std::min(header_only_size
.height(), kDialogMaxHeight
));
862 ExtensionInstallDialogView::~ExtensionInstallDialogView() {}
864 views::GridLayout
* ExtensionInstallDialogView::CreateLayout(
866 int left_column_width
,
868 bool single_detail_row
) const {
869 views::GridLayout
* layout
= views::GridLayout::CreatePanel(parent
);
870 parent
->SetLayoutManager(layout
);
872 views::ColumnSet
* column_set
= layout
->AddColumnSet(column_set_id
);
873 column_set
->AddColumn(views::GridLayout::LEADING
,
874 views::GridLayout::FILL
,
876 views::GridLayout::USE_PREF
,
879 if (!is_bundle_install()) {
880 column_set
->AddPaddingColumn(0, views::kPanelHorizMargin
);
881 column_set
->AddColumn(views::GridLayout::TRAILING
,
882 views::GridLayout::LEADING
,
884 views::GridLayout::USE_PREF
,
889 layout
->StartRow(0, column_set_id
);
891 ui::ResourceBundle
& rb
= ui::ResourceBundle::GetSharedInstance();
893 views::Label
* heading
= new views::Label(
894 prompt_
.GetHeading(), rb
.GetFontList(ui::ResourceBundle::MediumFont
));
895 heading
->SetMultiLine(true);
896 heading
->SetHorizontalAlignment(gfx::ALIGN_LEFT
);
897 heading
->SizeToFit(left_column_width
);
898 layout
->AddView(heading
);
900 if (!is_bundle_install()) {
901 // Scale down to icon size, but allow smaller icons (don't scale up).
902 const gfx::ImageSkia
* image
= prompt_
.icon().ToImageSkia();
903 gfx::Size
size(image
->width(), image
->height());
904 if (size
.width() > kIconSize
|| size
.height() > kIconSize
)
905 size
= gfx::Size(kIconSize
, kIconSize
);
906 views::ImageView
* icon
= new views::ImageView();
907 icon
->SetImageSize(size
);
908 icon
->SetImage(*image
);
909 icon
->SetHorizontalAlignment(views::ImageView::CENTER
);
910 icon
->SetVerticalAlignment(views::ImageView::CENTER
);
911 if (single_detail_row
) {
912 layout
->AddView(icon
);
914 int icon_row_span
= 1;
915 if (is_inline_install()) {
916 // Also span the rating, user_count and store_link rows.
918 } else if (prompt_
.ShouldShowPermissions()) {
919 size_t permission_count
= prompt_
.GetPermissionCount();
920 // Also span the permission header and each of the permission rows (all
921 // have a padding row above it). This also works for the 'no special
922 // permissions' case.
923 icon_row_span
= 3 + permission_count
* 2;
924 } else if (prompt_
.GetOAuthIssueCount()) {
925 // Also span the permission header and each of the permission rows (all
926 // have a padding row above it).
927 icon_row_span
= 3 + prompt_
.GetOAuthIssueCount() * 2;
928 } else if (prompt_
.GetRetainedFileCount()) {
929 // Also span the permission header and the retained files container.
932 layout
->AddView(icon
, 1, icon_row_span
);
938 void ExtensionInstallDialogView::ContentsChanged() {
942 void ExtensionInstallDialogView::ViewHierarchyChanged(
943 const ViewHierarchyChangedDetails
& details
) {
944 // Since we want the links to show up in the same visual row as the accept
945 // and cancel buttons, which is provided by the framework, we must add the
946 // buttons to the non-client view, which is the parent of this view.
947 // Similarly, when we're removed from the view hierarchy, we must take care
948 // to clean up those items as well.
949 if (details
.child
== this) {
950 if (details
.is_add
) {
951 if (show_details_link_
)
952 details
.parent
->AddChildView(show_details_link_
);
953 if (checkbox_info_label_
)
954 details
.parent
->AddChildView(checkbox_info_label_
);
956 if (show_details_link_
)
957 details
.parent
->RemoveChildView(show_details_link_
);
958 if (checkbox_info_label_
)
959 details
.parent
->RemoveChildView(checkbox_info_label_
);
964 int ExtensionInstallDialogView::GetDialogButtons() const {
965 int buttons
= prompt_
.GetDialogButtons();
966 // Simply having just an OK button is *not* supported. See comment on function
967 // GetDialogButtons in dialog_delegate.h for reasons.
968 DCHECK_GT(buttons
& ui::DIALOG_BUTTON_CANCEL
, 0);
972 base::string16
ExtensionInstallDialogView::GetDialogButtonLabel(
973 ui::DialogButton button
) const {
975 case ui::DIALOG_BUTTON_OK
:
976 return prompt_
.GetAcceptButtonLabel();
977 case ui::DIALOG_BUTTON_CANCEL
:
978 return prompt_
.HasAbortButtonLabel() ?
979 prompt_
.GetAbortButtonLabel() :
980 l10n_util::GetStringUTF16(IDS_CANCEL
);
983 return base::string16();
987 int ExtensionInstallDialogView::GetDefaultDialogButton() const {
988 return ui::DIALOG_BUTTON_CANCEL
;
991 bool ExtensionInstallDialogView::Cancel() {
992 UpdateInstallResultHistogram(false);
993 delegate_
->InstallUIAbort(true);
997 bool ExtensionInstallDialogView::Accept() {
998 UpdateInstallResultHistogram(true);
999 delegate_
->InstallUIProceed();
1003 ui::ModalType
ExtensionInstallDialogView::GetModalType() const {
1004 return ui::MODAL_TYPE_WINDOW
;
1007 base::string16
ExtensionInstallDialogView::GetWindowTitle() const {
1008 return prompt_
.GetDialogTitle();
1011 void ExtensionInstallDialogView::LinkClicked(views::Link
* source
,
1013 if (source
== show_details_link_
) {
1014 UpdateLinkActionHistogram(LINK_CLICKED
);
1015 // Show details link is used to either reveal whole permission list or to
1016 // reveal inline explanations.
1017 if (prompt_
.experiment()->should_show_expandable_permission_list()) {
1018 gfx::Rect bounds
= GetWidget()->GetWindowBoundsInScreen();
1019 int spacing
= bounds
.height() -
1020 scrollable_header_only_
->GetPreferredSize().height();
1021 int content_height
= std::min(scrollable_
->GetPreferredSize().height(),
1023 bounds
.set_height(spacing
+ content_height
);
1024 scroll_view_
->SetContents(scrollable_
);
1025 GetWidget()->SetBoundsConstrained(bounds
);
1028 ToggleInlineExplanations();
1030 show_details_link_
->SetVisible(false);
1032 GURL
store_url(extension_urls::GetWebstoreItemDetailURLPrefix() +
1033 prompt_
.extension()->id());
1034 OpenURLParams
params(
1035 store_url
, Referrer(), NEW_FOREGROUND_TAB
,
1036 content::PAGE_TRANSITION_LINK
,
1038 navigator_
->OpenURL(params
);
1039 GetWidget()->Close();
1043 void ExtensionInstallDialogView::ToggleInlineExplanations() {
1044 for (InlineExplanations::iterator it
= inline_explanations_
.begin();
1045 it
!= inline_explanations_
.end(); ++it
)
1046 (*it
)->ToggleDetailLevel();
1049 void ExtensionInstallDialogView::Layout() {
1050 scroll_view_
->SetBounds(0, 0, width(), height());
1052 if (show_details_link_
|| checkbox_info_label_
) {
1053 views::LabelButton
* cancel_button
= GetDialogClientView()->cancel_button();
1054 gfx::Rect parent_bounds
= parent()->GetContentsBounds();
1055 // By default, layouts have an inset of kButtonHEdgeMarginNew. In order to
1056 // align the link horizontally with the left side of the contents of the
1057 // layout, put a horizontal margin with this amount.
1058 const int horizontal_margin
= views::kButtonHEdgeMarginNew
;
1059 const int vertical_margin
= views::kButtonVEdgeMarginNew
;
1060 int y_buttons
= parent_bounds
.bottom() -
1061 cancel_button
->GetPreferredSize().height() - vertical_margin
;
1062 int max_width
= dialog_size_
.width() - cancel_button
->width() * 2 -
1063 horizontal_margin
* 2 - views::kRelatedButtonHSpacing
;
1064 if (show_details_link_
) {
1065 gfx::Size link_size
= show_details_link_
->GetPreferredSize();
1066 show_details_link_
->SetBounds(
1068 y_buttons
+ (cancel_button
->height() - link_size
.height()) / 2,
1069 link_size
.width(), link_size
.height());
1071 if (checkbox_info_label_
) {
1072 gfx::Size label_size
= checkbox_info_label_
->GetPreferredSize();
1073 checkbox_info_label_
->SetBounds(
1075 y_buttons
+ (cancel_button
->height() - label_size
.height()) / 2,
1076 label_size
.width(), label_size
.height());
1077 checkbox_info_label_
->SizeToFit(max_width
);
1080 // Disable accept button if there are unchecked boxes and
1081 // the experiment is on.
1082 if (prompt_
.experiment()->show_checkboxes())
1083 GetDialogClientView()->ok_button()->SetEnabled(unchecked_boxes_
== 0);
1085 DialogDelegateView::Layout();
1088 gfx::Size
ExtensionInstallDialogView::GetPreferredSize() const {
1089 return dialog_size_
;
1092 void ExtensionInstallDialogView::ButtonPressed(views::Button
* sender
,
1093 const ui::Event
& event
) {
1094 if (std::string(views::Checkbox::kViewClassName
) == sender
->GetClassName()) {
1095 views::Checkbox
* checkbox
= static_cast<views::Checkbox
*>(sender
);
1096 if (checkbox
->checked())
1101 GetDialogClientView()->ok_button()->SetEnabled(unchecked_boxes_
== 0);
1102 checkbox_info_label_
->SetVisible(unchecked_boxes_
> 0);
1106 void ExtensionInstallDialogView::UpdateInstallResultHistogram(bool accepted
)
1108 if (prompt_
.type() == ExtensionInstallPrompt::INSTALL_PROMPT
)
1109 UMA_HISTOGRAM_BOOLEAN("Extensions.InstallPrompt.Accepted", accepted
);
1112 void ExtensionInstallDialogView::UpdateLinkActionHistogram(int action_type
)
1114 if (prompt_
.experiment()->should_show_expandable_permission_list()) {
1115 // The clickable link in the UI is "Show Permissions".
1116 UMA_HISTOGRAM_ENUMERATION(
1117 "Extensions.InstallPromptExperiment.ShowPermissions",
1121 // The clickable link in the UI is "Show Details".
1122 UMA_HISTOGRAM_ENUMERATION(
1123 "Extensions.InstallPromptExperiment.ShowDetails",
1130 ExtensionInstallPrompt::ShowDialogCallback
1131 ExtensionInstallPrompt::GetDefaultShowDialogCallback() {
1132 return base::Bind(&ShowExtensionInstallDialogImpl
);
1135 // ExpandableContainerView::DetailsView ----------------------------------------
1137 ExpandableContainerView::DetailsView::DetailsView(int horizontal_space
,
1138 bool parent_bulleted
,
1140 : layout_(new views::GridLayout(this)),
1142 lighter_color_(lighter_color
) {
1143 SetLayoutManager(layout_
);
1144 views::ColumnSet
* column_set
= layout_
->AddColumnSet(0);
1145 // If the parent is using bullets for its items, then a padding of one unit
1146 // will make the child item (which has no bullet) look like a sibling of its
1147 // parent. Therefore increase the indentation by one more unit to show that it
1148 // is in fact a child item (with no missing bullet) and not a sibling.
1150 views::kRelatedControlHorizontalSpacing
* (parent_bulleted
? 2 : 1);
1151 column_set
->AddPaddingColumn(0, padding
);
1152 column_set
->AddColumn(views::GridLayout::LEADING
,
1153 views::GridLayout::LEADING
,
1155 views::GridLayout::FIXED
,
1156 horizontal_space
- padding
,
1160 void ExpandableContainerView::DetailsView::AddDetail(
1161 const base::string16
& detail
) {
1162 layout_
->StartRowWithPadding(0, 0,
1163 0, views::kRelatedControlSmallVerticalSpacing
);
1164 views::Label
* detail_label
=
1165 new views::Label(PrepareForDisplay(detail
, false));
1166 detail_label
->SetMultiLine(true);
1167 detail_label
->SetHorizontalAlignment(gfx::ALIGN_LEFT
);
1168 if (lighter_color_
) {
1169 detail_label
->SetEnabledColor(kLighterLabelColor
);
1170 detail_label
->SetAutoColorReadabilityEnabled(false);
1172 layout_
->AddView(detail_label
);
1175 gfx::Size
ExpandableContainerView::DetailsView::GetPreferredSize() const {
1176 gfx::Size size
= views::View::GetPreferredSize();
1177 return gfx::Size(size
.width(), size
.height() * state_
);
1180 void ExpandableContainerView::DetailsView::AnimateToState(double state
) {
1182 PreferredSizeChanged();
1186 // ExpandableContainerView -----------------------------------------------------
1188 ExpandableContainerView::ExpandableContainerView(
1189 ExtensionInstallDialogView
* owner
,
1190 const base::string16
& description
,
1191 const PermissionDetails
& details
,
1192 int horizontal_space
,
1193 bool parent_bulleted
,
1194 bool show_expand_link
,
1195 bool lighter_color_details
)
1197 details_view_(NULL
),
1198 more_details_(NULL
),
1199 slide_animation_(this),
1200 arrow_toggle_(NULL
),
1202 views::GridLayout
* layout
= new views::GridLayout(this);
1203 SetLayoutManager(layout
);
1204 int column_set_id
= 0;
1205 views::ColumnSet
* column_set
= layout
->AddColumnSet(column_set_id
);
1206 column_set
->AddColumn(views::GridLayout::LEADING
,
1207 views::GridLayout::LEADING
,
1209 views::GridLayout::USE_PREF
,
1212 if (!description
.empty()) {
1213 layout
->StartRow(0, column_set_id
);
1215 views::Label
* description_label
= new views::Label(description
);
1216 description_label
->SetMultiLine(true);
1217 description_label
->SetHorizontalAlignment(gfx::ALIGN_LEFT
);
1218 description_label
->SizeToFit(horizontal_space
);
1219 layout
->AddView(new BulletedView(description_label
));
1222 if (details
.empty())
1225 details_view_
= new DetailsView(horizontal_space
, parent_bulleted
,
1226 lighter_color_details
);
1228 layout
->StartRow(0, column_set_id
);
1229 layout
->AddView(details_view_
);
1231 for (size_t i
= 0; i
< details
.size(); ++i
)
1232 details_view_
->AddDetail(details
[i
]);
1234 // TODO(meacer): Remove show_expand_link when the experiment is completed.
1235 if (show_expand_link
) {
1236 views::Link
* link
= new views::Link(
1237 l10n_util::GetStringUTF16(IDS_EXTENSIONS_SHOW_DETAILS
));
1239 // Make sure the link width column is as wide as needed for both Show and
1240 // Hide details, so that the arrow doesn't shift horizontally when we
1242 int link_col_width
=
1243 views::kRelatedControlHorizontalSpacing
+
1244 std::max(gfx::GetStringWidth(
1245 l10n_util::GetStringUTF16(IDS_EXTENSIONS_HIDE_DETAILS
),
1247 gfx::GetStringWidth(
1248 l10n_util::GetStringUTF16(IDS_EXTENSIONS_SHOW_DETAILS
),
1249 link
->font_list()));
1251 column_set
= layout
->AddColumnSet(++column_set_id
);
1252 // Padding to the left of the More Details column. If the parent is using
1253 // bullets for its items, then a padding of one unit will make the child
1254 // item (which has no bullet) look like a sibling of its parent. Therefore
1255 // increase the indentation by one more unit to show that it is in fact a
1256 // child item (with no missing bullet) and not a sibling.
1257 column_set
->AddPaddingColumn(
1258 0, views::kRelatedControlHorizontalSpacing
* (parent_bulleted
? 2 : 1));
1259 // The More Details column.
1260 column_set
->AddColumn(views::GridLayout::LEADING
,
1261 views::GridLayout::LEADING
,
1263 views::GridLayout::FIXED
,
1266 // The Up/Down arrow column.
1267 column_set
->AddColumn(views::GridLayout::LEADING
,
1268 views::GridLayout::LEADING
,
1270 views::GridLayout::USE_PREF
,
1274 // Add the More Details link.
1275 layout
->StartRow(0, column_set_id
);
1276 more_details_
= link
;
1277 more_details_
->set_listener(this);
1278 more_details_
->SetHorizontalAlignment(gfx::ALIGN_LEFT
);
1279 layout
->AddView(more_details_
);
1281 // Add the arrow after the More Details link.
1282 ui::ResourceBundle
& rb
= ui::ResourceBundle::GetSharedInstance();
1283 arrow_toggle_
= new views::ImageButton(this);
1284 arrow_toggle_
->SetImage(views::Button::STATE_NORMAL
,
1285 rb
.GetImageSkiaNamed(IDR_DOWN_ARROW
));
1286 layout
->AddView(arrow_toggle_
);
1290 ExpandableContainerView::~ExpandableContainerView() {
1293 void ExpandableContainerView::ButtonPressed(
1294 views::Button
* sender
, const ui::Event
& event
) {
1295 ToggleDetailLevel();
1298 void ExpandableContainerView::LinkClicked(
1299 views::Link
* source
, int event_flags
) {
1300 ToggleDetailLevel();
1303 void ExpandableContainerView::AnimationProgressed(
1304 const gfx::Animation
* animation
) {
1305 DCHECK_EQ(&slide_animation_
, animation
);
1307 details_view_
->AnimateToState(animation
->GetCurrentValue());
1310 void ExpandableContainerView::AnimationEnded(const gfx::Animation
* animation
) {
1311 if (arrow_toggle_
) {
1312 if (animation
->GetCurrentValue() != 0.0) {
1313 arrow_toggle_
->SetImage(
1314 views::Button::STATE_NORMAL
,
1315 ui::ResourceBundle::GetSharedInstance().GetImageSkiaNamed(
1318 arrow_toggle_
->SetImage(
1319 views::Button::STATE_NORMAL
,
1320 ui::ResourceBundle::GetSharedInstance().GetImageSkiaNamed(
1324 if (more_details_
) {
1325 more_details_
->SetText(expanded_
?
1326 l10n_util::GetStringUTF16(IDS_EXTENSIONS_HIDE_DETAILS
) :
1327 l10n_util::GetStringUTF16(IDS_EXTENSIONS_SHOW_DETAILS
));
1331 void ExpandableContainerView::ChildPreferredSizeChanged(views::View
* child
) {
1332 owner_
->ContentsChanged();
1335 void ExpandableContainerView::ToggleDetailLevel() {
1336 expanded_
= !expanded_
;
1338 if (slide_animation_
.IsShowing())
1339 slide_animation_
.Hide();
1341 slide_animation_
.Show();
1344 void ExpandableContainerView::ExpandWithoutAnimation() {
1346 details_view_
->AnimateToState(1.0);