Resized the icon in the Uninstall dialog to be 64x64 px
[chromium-blink-merge.git] / chrome / browser / ui / views / extensions / extension_install_dialog_view.cc
blobf0c9fc3836d3fc0618cbb57ed58e644a846b9a51
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 <vector>
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;
58 namespace {
60 // Size of extension icon in top left of dialog.
61 const int kIconSize = 64;
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
72 // permissions.
73 const int kPermissionsLeftColumnWidth = 250;
75 // Width of the left column of the dialog when the extension requests no
76 // permissions.
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 {
95 LINK_SHOWN = 0,
96 LINK_NOT_SHOWN,
97 LINK_CLICKED,
98 NUM_LINK_ACTIONS
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,
114 bool bullet_point) {
115 return bullet_point ? l10n_util::GetStringFUTF16(
116 IDS_EXTENSION_PERMISSION_LINE,
117 message) : message;
120 // A custom scrollable view implementation for the dialog.
121 class CustomScrollableView : public views::View {
122 public:
123 CustomScrollableView();
124 virtual ~CustomScrollableView();
126 private:
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 {
136 public:
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();
145 private:
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(
172 views::View* parent,
173 int left_column_width,
174 int column_set_id,
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
216 // permision dialog.
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
234 // layout.
235 class BulletedView : public views::View {
236 public:
237 explicit BulletedView(views::View* view);
238 private:
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,
250 0, // no fixed width
252 column_set->AddColumn(views::GridLayout::LEADING,
253 views::GridLayout::LEADING,
255 views::GridLayout::USE_PREF,
256 0, // no fixed width
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 {
267 public:
268 CheckboxedView(views::View* view, views::ButtonListener* listener);
269 private:
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,
282 0, // no fixed width
284 column_set->AddColumn(views::GridLayout::LEADING,
285 views::GridLayout::LEADING,
287 views::GridLayout::USE_PREF,
288 0, // no fixed width
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 {
306 public:
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();
316 // views::View:
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();
337 private:
338 // A view which displays all the details of an IssueAdviceInfoEntry.
339 class DetailsView : public views::View {
340 public:
341 explicit DetailsView(int horizontal_space, bool parent_bulleted,
342 bool lighter_color);
343 virtual ~DetailsView() {}
345 // views::View:
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);
353 private:
354 views::GridLayout* layout_;
355 double state_;
357 // Whether the detail text should be shown with a lighter color.
358 bool 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.
380 bool 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();
395 } // namespace
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),
410 delegate_(delegate),
411 prompt_(prompt),
412 scroll_view_(NULL),
413 scrollable_(NULL),
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:
419 // Inline install
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 // +--------------------+------+ +--------------+------+
430 // | separator |
431 // +--------------------+------+
432 // | permissions_header | |
433 // +--------------------+------+
434 // | permission1 | |
435 // +--------------------+------+
436 // | permission2 | |
437 // +--------------------+------+
439 // Regular install
440 // w/ permissions no permissions
441 // +--------------------+------+ +--------------+------+
442 // | heading | icon | | heading | icon |
443 // +--------------------| | +--------------+------+
444 // | permissions_header | |
445 // +--------------------| |
446 // | permission1 | |
447 // +--------------------| |
448 // | permission2 | |
449 // +--------------------+------+
451 // If the ExtensionPermissionDialog is on, the layout is modified depending
452 // on the experiment group. For text only experiment, a footer is added at the
453 // bottom of the layouts. For others, inline details are added below some of
454 // the permissions.
456 // Regular install w/ permissions and footer (experiment):
457 // +--------------------+------+
458 // | heading | icon |
459 // +--------------------| |
460 // | permissions_header | |
461 // +--------------------| |
462 // | permission1 | |
463 // +--------------------| |
464 // | permission2 | |
465 // +--------------------+------+
466 // | footer text | |
467 // +--------------------+------+
469 // Regular install w/ permissions and inline explanations (experiment):
470 // +--------------------+------+
471 // | heading | icon |
472 // +--------------------| |
473 // | permissions_header | |
474 // +--------------------| |
475 // | permission1 | |
476 // +--------------------| |
477 // | explanation1 | |
478 // +--------------------| |
479 // | permission2 | |
480 // +--------------------| |
481 // | explanation2 | |
482 // +--------------------+------+
484 // Regular install w/ permissions and inline explanations (experiment):
485 // +--------------------+------+
486 // | heading | icon |
487 // +--------------------| |
488 // | permissions_header | |
489 // +--------------------| |
490 // |checkbox|permission1| |
491 // +--------------------| |
492 // |checkbox|permission2| |
493 // +--------------------+------+
495 // Additionally, links or informational text is added to non-client areas of
496 // the dialog depending on the experiment group.
498 int left_column_width =
499 (prompt.ShouldShowPermissions() +
500 prompt.GetRetainedFileCount()) > 0 ?
501 kPermissionsLeftColumnWidth : kNoPermissionsLeftColumnWidth;
502 if (is_bundle_install())
503 left_column_width = kBundleLeftColumnWidth;
504 if (is_external_install())
505 left_column_width = kExternalInstallLeftColumnWidth;
507 scroll_view_ = new views::ScrollView();
508 scroll_view_->set_hide_horizontal_scrollbar(true);
509 AddChildView(scroll_view_);
511 int column_set_id = 0;
512 // Create the full scrollable view which will contain all the information
513 // including the permissions.
514 scrollable_ = new CustomScrollableView();
515 views::GridLayout* layout = CreateLayout(
516 scrollable_, left_column_width, column_set_id, false);
517 ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();
519 if (prompt.ShouldShowPermissions() &&
520 prompt.experiment()->should_show_expandable_permission_list()) {
521 // If the experiment should hide the permission list initially, create a
522 // simple layout that contains only the header, extension name and icon.
523 scrollable_header_only_ = new CustomScrollableView();
524 CreateLayout(scrollable_header_only_, left_column_width,
525 column_set_id, true);
526 scroll_view_->SetContents(scrollable_header_only_);
527 } else {
528 scroll_view_->SetContents(scrollable_);
531 int dialog_width = left_column_width + 2 * views::kPanelHorizMargin;
532 if (!is_bundle_install())
533 dialog_width += views::kPanelHorizMargin + kIconSize + kIconOffset;
535 // Widen the dialog for experiment with checkboxes so that the information
536 // label fits the area to the left of the buttons.
537 if (prompt.experiment()->show_checkboxes())
538 dialog_width += 4 * views::kPanelHorizMargin;
540 if (prompt.has_webstore_data()) {
541 layout->StartRow(0, column_set_id);
542 views::View* rating = new views::View();
543 rating->SetLayoutManager(new views::BoxLayout(
544 views::BoxLayout::kHorizontal, 0, 0, 0));
545 layout->AddView(rating);
546 prompt.AppendRatingStars(AddResourceIcon, rating);
548 const gfx::FontList& small_font_list =
549 rb.GetFontList(ui::ResourceBundle::SmallFont);
550 views::Label* rating_count =
551 new views::Label(prompt.GetRatingCount(), small_font_list);
552 // Add some space between the stars and the rating count.
553 rating_count->SetBorder(views::Border::CreateEmptyBorder(0, 2, 0, 0));
554 rating->AddChildView(rating_count);
556 layout->StartRow(0, column_set_id);
557 views::Label* user_count =
558 new views::Label(prompt.GetUserCount(), small_font_list);
559 user_count->SetAutoColorReadabilityEnabled(false);
560 user_count->SetEnabledColor(SK_ColorGRAY);
561 layout->AddView(user_count);
563 layout->StartRow(0, column_set_id);
564 views::Link* store_link = new views::Link(
565 l10n_util::GetStringUTF16(IDS_EXTENSION_PROMPT_STORE_LINK));
566 store_link->SetFontList(small_font_list);
567 store_link->set_listener(this);
568 layout->AddView(store_link);
571 if (is_bundle_install()) {
572 BundleInstaller::ItemList items = prompt.bundle()->GetItemsWithState(
573 BundleInstaller::Item::STATE_PENDING);
574 for (size_t i = 0; i < items.size(); ++i) {
575 base::string16 extension_name =
576 base::UTF8ToUTF16(items[i].localized_name);
577 base::i18n::AdjustStringForLocaleDirection(&extension_name);
578 layout->AddPaddingRow(0, views::kRelatedControlVerticalSpacing);
579 layout->StartRow(0, column_set_id);
580 views::Label* extension_label = new views::Label(
581 PrepareForDisplay(extension_name, true));
582 extension_label->SetMultiLine(true);
583 extension_label->SetHorizontalAlignment(gfx::ALIGN_LEFT);
584 extension_label->SizeToFit(left_column_width);
585 layout->AddView(extension_label);
589 if (prompt.ShouldShowPermissions()) {
590 layout->AddPaddingRow(0, views::kRelatedControlVerticalSpacing);
592 if (prompt.GetPermissionCount() > 0) {
593 if (is_inline_install()) {
594 layout->StartRow(0, column_set_id);
595 layout->AddView(new views::Separator(views::Separator::HORIZONTAL),
596 3, 1, views::GridLayout::FILL, views::GridLayout::FILL);
597 layout->AddPaddingRow(0, views::kRelatedControlVerticalSpacing);
600 layout->StartRow(0, column_set_id);
601 views::Label* permissions_header = NULL;
602 if (is_bundle_install()) {
603 // We need to pass the FontList in the constructor, rather than calling
604 // SetFontList later, because otherwise SizeToFit mis-judges the width
605 // of the line.
606 permissions_header = new views::Label(
607 prompt.GetPermissionsHeading(),
608 rb.GetFontList(ui::ResourceBundle::MediumFont));
609 } else {
610 permissions_header = new views::Label(prompt.GetPermissionsHeading());
612 permissions_header->SetMultiLine(true);
613 permissions_header->SetHorizontalAlignment(gfx::ALIGN_LEFT);
614 permissions_header->SizeToFit(left_column_width);
615 layout->AddView(permissions_header);
617 for (size_t i = 0; i < prompt.GetPermissionCount(); ++i) {
618 layout->AddPaddingRow(0, views::kRelatedControlVerticalSpacing);
619 layout->StartRow(0, column_set_id);
620 views::Label* permission_label =
621 new views::Label(prompt.GetPermission(i));
623 const SkColor kTextHighlight = SK_ColorRED;
624 const SkColor kBackgroundHighlight = SkColorSetRGB(0xFB, 0xF7, 0xA3);
625 if (prompt.experiment()->ShouldHighlightText(
626 prompt.GetPermission(i))) {
627 permission_label->SetAutoColorReadabilityEnabled(false);
628 permission_label->SetEnabledColor(kTextHighlight);
629 } else if (prompt.experiment()->ShouldHighlightBackground(
630 prompt.GetPermission(i))) {
631 permission_label->SetLineHeight(18);
632 permission_label->set_background(
633 views::Background::CreateSolidBackground(kBackgroundHighlight));
636 permission_label->SetMultiLine(true);
637 permission_label->SetHorizontalAlignment(gfx::ALIGN_LEFT);
638 permission_label->SizeToFit(left_column_width);
640 if (prompt.experiment()->show_checkboxes()) {
641 layout->AddView(new CheckboxedView(permission_label, this));
642 ++unchecked_boxes_;
643 } else {
644 layout->AddView(new BulletedView(permission_label));
646 // If we have more details to provide, show them in collapsed form.
647 if (!prompt.GetPermissionsDetails(i).empty()) {
648 layout->StartRow(0, column_set_id);
649 PermissionDetails details;
650 details.push_back(
651 PrepareForDisplay(prompt.GetPermissionsDetails(i), false));
652 ExpandableContainerView* details_container =
653 new ExpandableContainerView(
654 this, base::string16(), details, left_column_width,
655 true, true, false);
656 layout->AddView(details_container);
659 if (prompt.experiment()->should_show_inline_explanations()) {
660 base::string16 explanation =
661 prompt.experiment()->GetInlineExplanation(
662 prompt.GetPermission(i));
663 if (!explanation.empty()) {
664 PermissionDetails details;
665 details.push_back(explanation);
666 ExpandableContainerView* container =
667 new ExpandableContainerView(this, base::string16(), details,
668 left_column_width,
669 false, false, true);
670 // Inline explanations are expanded by default if there is
671 // no "Show details" link.
672 if (!prompt.experiment()->show_details_link())
673 container->ExpandWithoutAnimation();
674 layout->StartRow(0, column_set_id);
675 layout->AddView(container);
676 inline_explanations_.push_back(container);
680 } else {
681 layout->AddPaddingRow(0, views::kRelatedControlVerticalSpacing);
682 layout->StartRow(0, column_set_id);
683 views::Label* permission_label = new views::Label(
684 l10n_util::GetStringUTF16(IDS_EXTENSION_NO_SPECIAL_PERMISSIONS));
685 permission_label->SetMultiLine(true);
686 permission_label->SetHorizontalAlignment(gfx::ALIGN_LEFT);
687 permission_label->SizeToFit(left_column_width);
688 layout->AddView(permission_label);
692 if (prompt.GetRetainedFileCount()) {
693 // Slide in under the permissions, if there are any. If there are
694 // either, the retained files prompt stretches all the way to the
695 // right of the dialog. If there are no permissions, the retained
696 // files prompt just takes up the left column.
697 int space_for_files = left_column_width;
698 if (prompt.GetPermissionCount()) {
699 space_for_files += kIconSize;
700 views::ColumnSet* column_set = layout->AddColumnSet(++column_set_id);
701 column_set->AddColumn(views::GridLayout::FILL,
702 views::GridLayout::FILL,
704 views::GridLayout::USE_PREF,
705 0, // no fixed width
706 space_for_files);
709 layout->AddPaddingRow(0, views::kRelatedControlVerticalSpacing);
711 layout->StartRow(0, column_set_id);
712 views::Label* retained_files_header = NULL;
713 retained_files_header =
714 new views::Label(prompt.GetRetainedFilesHeading());
715 retained_files_header->SetMultiLine(true);
716 retained_files_header->SetHorizontalAlignment(gfx::ALIGN_LEFT);
717 retained_files_header->SizeToFit(space_for_files);
718 layout->AddView(retained_files_header);
720 layout->StartRow(0, column_set_id);
721 PermissionDetails details;
722 for (size_t i = 0; i < prompt.GetRetainedFileCount(); ++i)
723 details.push_back(prompt.GetRetainedFile(i));
724 ExpandableContainerView* issue_advice_view =
725 new ExpandableContainerView(
726 this, base::string16(), details, space_for_files,
727 false, true, false);
728 layout->AddView(issue_advice_view);
731 DCHECK(prompt.type() >= 0);
732 UMA_HISTOGRAM_ENUMERATION("Extensions.InstallPrompt.Type",
733 prompt.type(),
734 ExtensionInstallPrompt::NUM_PROMPT_TYPES);
736 if (prompt.ShouldShowPermissions()) {
737 if (prompt.ShouldShowExplanationText()) {
738 views::ColumnSet* column_set = layout->AddColumnSet(++column_set_id);
739 column_set->AddColumn(views::GridLayout::LEADING,
740 views::GridLayout::FILL,
742 views::GridLayout::USE_PREF,
745 // Add two rows of space so that the text stands out.
746 layout->AddPaddingRow(0, 2 * views::kRelatedControlVerticalSpacing);
748 layout->StartRow(0, column_set_id);
749 views::Label* explanation = new views::Label(
750 prompt.experiment()->GetExplanationText());
751 explanation->SetMultiLine(true);
752 explanation->SetHorizontalAlignment(gfx::ALIGN_LEFT);
753 explanation->SizeToFit(left_column_width + kIconSize);
754 layout->AddView(explanation);
757 if (prompt.experiment()->should_show_expandable_permission_list() ||
758 (prompt.experiment()->show_details_link() &&
759 prompt.experiment()->should_show_inline_explanations() &&
760 !inline_explanations_.empty())) {
761 // Don't show the "Show details" link if there are retained
762 // files. These have their own "Show details" links and having
763 // multiple levels of links is confusing.
764 if (prompt.GetRetainedFileCount() == 0) {
765 int text_id =
766 prompt.experiment()->should_show_expandable_permission_list() ?
767 IDS_EXTENSION_PROMPT_EXPERIMENT_SHOW_PERMISSIONS :
768 IDS_EXTENSION_PROMPT_EXPERIMENT_SHOW_DETAILS;
769 show_details_link_ = new views::Link(
770 l10n_util::GetStringUTF16(text_id));
771 show_details_link_->SetHorizontalAlignment(gfx::ALIGN_LEFT);
772 show_details_link_->set_listener(this);
773 UpdateLinkActionHistogram(LINK_SHOWN);
774 } else {
775 UpdateLinkActionHistogram(LINK_NOT_SHOWN);
779 if (prompt.experiment()->show_checkboxes()) {
780 checkbox_info_label_ = new views::Label(
781 l10n_util::GetStringUTF16(
782 IDS_EXTENSION_PROMPT_EXPERIMENT_CHECKBOX_INFO));
783 checkbox_info_label_->SetMultiLine(true);
784 checkbox_info_label_->SetHorizontalAlignment(gfx::ALIGN_LEFT);
785 checkbox_info_label_->SetAutoColorReadabilityEnabled(false);
786 checkbox_info_label_->SetEnabledColor(kLighterLabelColor);
790 gfx::Size scrollable_size = scrollable_->GetPreferredSize();
791 scrollable_->SetBoundsRect(gfx::Rect(scrollable_size));
792 dialog_size_ = gfx::Size(
793 dialog_width,
794 std::min(scrollable_size.height(), kDialogMaxHeight));
796 if (scrollable_header_only_) {
797 gfx::Size header_only_size = scrollable_header_only_->GetPreferredSize();
798 scrollable_header_only_->SetBoundsRect(gfx::Rect(header_only_size));
799 dialog_size_ = gfx::Size(
800 dialog_width, std::min(header_only_size.height(), kDialogMaxHeight));
804 ExtensionInstallDialogView::~ExtensionInstallDialogView() {}
806 views::GridLayout* ExtensionInstallDialogView::CreateLayout(
807 views::View* parent,
808 int left_column_width,
809 int column_set_id,
810 bool single_detail_row) const {
811 views::GridLayout* layout = views::GridLayout::CreatePanel(parent);
812 parent->SetLayoutManager(layout);
814 views::ColumnSet* column_set = layout->AddColumnSet(column_set_id);
815 column_set->AddColumn(views::GridLayout::LEADING,
816 views::GridLayout::FILL,
817 0, // no resizing
818 views::GridLayout::USE_PREF,
819 0, // no fixed width
820 left_column_width);
821 if (!is_bundle_install()) {
822 column_set->AddPaddingColumn(0, views::kPanelHorizMargin);
823 column_set->AddColumn(views::GridLayout::TRAILING,
824 views::GridLayout::LEADING,
825 0, // no resizing
826 views::GridLayout::USE_PREF,
827 0, // no fixed width
828 kIconSize);
831 layout->StartRow(0, column_set_id);
833 ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();
835 views::Label* heading = new views::Label(
836 prompt_.GetHeading(), rb.GetFontList(ui::ResourceBundle::MediumFont));
837 heading->SetMultiLine(true);
838 heading->SetHorizontalAlignment(gfx::ALIGN_LEFT);
839 heading->SizeToFit(left_column_width);
840 layout->AddView(heading);
842 if (!is_bundle_install()) {
843 // Scale down to icon size, but allow smaller icons (don't scale up).
844 const gfx::ImageSkia* image = prompt_.icon().ToImageSkia();
845 gfx::Size size(image->width(), image->height());
846 if (size.width() > kIconSize || size.height() > kIconSize)
847 size = gfx::Size(kIconSize, kIconSize);
848 views::ImageView* icon = new views::ImageView();
849 icon->SetImageSize(size);
850 icon->SetImage(*image);
851 icon->SetHorizontalAlignment(views::ImageView::CENTER);
852 icon->SetVerticalAlignment(views::ImageView::CENTER);
853 if (single_detail_row) {
854 layout->AddView(icon);
855 } else {
856 int icon_row_span = 1;
857 if (is_inline_install()) {
858 // Also span the rating, user_count and store_link rows.
859 icon_row_span = 4;
860 } else if (prompt_.ShouldShowPermissions()) {
861 size_t permission_count = prompt_.GetPermissionCount();
862 // Also span the permission header and each of the permission rows (all
863 // have a padding row above it). This also works for the 'no special
864 // permissions' case.
865 icon_row_span = 3 + permission_count * 2;
866 } else if (prompt_.GetRetainedFileCount()) {
867 // Also span the permission header and the retained files container.
868 icon_row_span = 4;
870 layout->AddView(icon, 1, icon_row_span);
873 return layout;
876 void ExtensionInstallDialogView::ContentsChanged() {
877 Layout();
880 void ExtensionInstallDialogView::ViewHierarchyChanged(
881 const ViewHierarchyChangedDetails& details) {
882 // Since we want the links to show up in the same visual row as the accept
883 // and cancel buttons, which is provided by the framework, we must add the
884 // buttons to the non-client view, which is the parent of this view.
885 // Similarly, when we're removed from the view hierarchy, we must take care
886 // to clean up those items as well.
887 if (details.child == this) {
888 if (details.is_add) {
889 if (show_details_link_)
890 details.parent->AddChildView(show_details_link_);
891 if (checkbox_info_label_)
892 details.parent->AddChildView(checkbox_info_label_);
893 } else {
894 if (show_details_link_)
895 details.parent->RemoveChildView(show_details_link_);
896 if (checkbox_info_label_)
897 details.parent->RemoveChildView(checkbox_info_label_);
902 int ExtensionInstallDialogView::GetDialogButtons() const {
903 int buttons = prompt_.GetDialogButtons();
904 // Simply having just an OK button is *not* supported. See comment on function
905 // GetDialogButtons in dialog_delegate.h for reasons.
906 DCHECK_GT(buttons & ui::DIALOG_BUTTON_CANCEL, 0);
907 return buttons;
910 base::string16 ExtensionInstallDialogView::GetDialogButtonLabel(
911 ui::DialogButton button) const {
912 switch (button) {
913 case ui::DIALOG_BUTTON_OK:
914 return prompt_.GetAcceptButtonLabel();
915 case ui::DIALOG_BUTTON_CANCEL:
916 return prompt_.HasAbortButtonLabel() ?
917 prompt_.GetAbortButtonLabel() :
918 l10n_util::GetStringUTF16(IDS_CANCEL);
919 default:
920 NOTREACHED();
921 return base::string16();
925 int ExtensionInstallDialogView::GetDefaultDialogButton() const {
926 return ui::DIALOG_BUTTON_CANCEL;
929 bool ExtensionInstallDialogView::Cancel() {
930 UpdateInstallResultHistogram(false);
931 delegate_->InstallUIAbort(true);
932 return true;
935 bool ExtensionInstallDialogView::Accept() {
936 UpdateInstallResultHistogram(true);
937 delegate_->InstallUIProceed();
938 return true;
941 ui::ModalType ExtensionInstallDialogView::GetModalType() const {
942 return ui::MODAL_TYPE_WINDOW;
945 base::string16 ExtensionInstallDialogView::GetWindowTitle() const {
946 return prompt_.GetDialogTitle();
949 void ExtensionInstallDialogView::LinkClicked(views::Link* source,
950 int event_flags) {
951 if (source == show_details_link_) {
952 UpdateLinkActionHistogram(LINK_CLICKED);
953 // Show details link is used to either reveal whole permission list or to
954 // reveal inline explanations.
955 if (prompt_.experiment()->should_show_expandable_permission_list()) {
956 gfx::Rect bounds = GetWidget()->GetWindowBoundsInScreen();
957 int spacing = bounds.height() -
958 scrollable_header_only_->GetPreferredSize().height();
959 int content_height = std::min(scrollable_->GetPreferredSize().height(),
960 kDialogMaxHeight);
961 bounds.set_height(spacing + content_height);
962 scroll_view_->SetContents(scrollable_);
963 GetWidget()->SetBoundsConstrained(bounds);
964 ContentsChanged();
965 } else {
966 ToggleInlineExplanations();
968 show_details_link_->SetVisible(false);
969 } else {
970 GURL store_url(extension_urls::GetWebstoreItemDetailURLPrefix() +
971 prompt_.extension()->id());
972 OpenURLParams params(
973 store_url, Referrer(), NEW_FOREGROUND_TAB,
974 content::PAGE_TRANSITION_LINK,
975 false);
976 navigator_->OpenURL(params);
977 GetWidget()->Close();
981 void ExtensionInstallDialogView::ToggleInlineExplanations() {
982 for (InlineExplanations::iterator it = inline_explanations_.begin();
983 it != inline_explanations_.end(); ++it)
984 (*it)->ToggleDetailLevel();
987 void ExtensionInstallDialogView::Layout() {
988 scroll_view_->SetBounds(0, 0, width(), height());
990 if (show_details_link_ || checkbox_info_label_) {
991 views::LabelButton* cancel_button = GetDialogClientView()->cancel_button();
992 gfx::Rect parent_bounds = parent()->GetContentsBounds();
993 // By default, layouts have an inset of kButtonHEdgeMarginNew. In order to
994 // align the link horizontally with the left side of the contents of the
995 // layout, put a horizontal margin with this amount.
996 const int horizontal_margin = views::kButtonHEdgeMarginNew;
997 const int vertical_margin = views::kButtonVEdgeMarginNew;
998 int y_buttons = parent_bounds.bottom() -
999 cancel_button->GetPreferredSize().height() - vertical_margin;
1000 int max_width = dialog_size_.width() - cancel_button->width() * 2 -
1001 horizontal_margin * 2 - views::kRelatedButtonHSpacing;
1002 if (show_details_link_) {
1003 gfx::Size link_size = show_details_link_->GetPreferredSize();
1004 show_details_link_->SetBounds(
1005 horizontal_margin,
1006 y_buttons + (cancel_button->height() - link_size.height()) / 2,
1007 link_size.width(), link_size.height());
1009 if (checkbox_info_label_) {
1010 gfx::Size label_size = checkbox_info_label_->GetPreferredSize();
1011 checkbox_info_label_->SetBounds(
1012 horizontal_margin,
1013 y_buttons + (cancel_button->height() - label_size.height()) / 2,
1014 label_size.width(), label_size.height());
1015 checkbox_info_label_->SizeToFit(max_width);
1018 // Disable accept button if there are unchecked boxes and
1019 // the experiment is on.
1020 if (prompt_.experiment()->show_checkboxes())
1021 GetDialogClientView()->ok_button()->SetEnabled(unchecked_boxes_ == 0);
1023 DialogDelegateView::Layout();
1026 gfx::Size ExtensionInstallDialogView::GetPreferredSize() const {
1027 return dialog_size_;
1030 void ExtensionInstallDialogView::ButtonPressed(views::Button* sender,
1031 const ui::Event& event) {
1032 if (std::string(views::Checkbox::kViewClassName) == sender->GetClassName()) {
1033 views::Checkbox* checkbox = static_cast<views::Checkbox*>(sender);
1034 if (checkbox->checked())
1035 --unchecked_boxes_;
1036 else
1037 ++unchecked_boxes_;
1039 GetDialogClientView()->ok_button()->SetEnabled(unchecked_boxes_ == 0);
1040 checkbox_info_label_->SetVisible(unchecked_boxes_ > 0);
1044 void ExtensionInstallDialogView::UpdateInstallResultHistogram(bool accepted)
1045 const {
1046 if (prompt_.type() == ExtensionInstallPrompt::INSTALL_PROMPT)
1047 UMA_HISTOGRAM_BOOLEAN("Extensions.InstallPrompt.Accepted", accepted);
1050 void ExtensionInstallDialogView::UpdateLinkActionHistogram(int action_type)
1051 const {
1052 if (prompt_.experiment()->should_show_expandable_permission_list()) {
1053 // The clickable link in the UI is "Show Permissions".
1054 UMA_HISTOGRAM_ENUMERATION(
1055 "Extensions.InstallPromptExperiment.ShowPermissions",
1056 action_type,
1057 NUM_LINK_ACTIONS);
1058 } else {
1059 // The clickable link in the UI is "Show Details".
1060 UMA_HISTOGRAM_ENUMERATION(
1061 "Extensions.InstallPromptExperiment.ShowDetails",
1062 action_type,
1063 NUM_LINK_ACTIONS);
1067 // static
1068 ExtensionInstallPrompt::ShowDialogCallback
1069 ExtensionInstallPrompt::GetDefaultShowDialogCallback() {
1070 return base::Bind(&ShowExtensionInstallDialogImpl);
1073 // ExpandableContainerView::DetailsView ----------------------------------------
1075 ExpandableContainerView::DetailsView::DetailsView(int horizontal_space,
1076 bool parent_bulleted,
1077 bool lighter_color)
1078 : layout_(new views::GridLayout(this)),
1079 state_(0),
1080 lighter_color_(lighter_color) {
1081 SetLayoutManager(layout_);
1082 views::ColumnSet* column_set = layout_->AddColumnSet(0);
1083 // If the parent is using bullets for its items, then a padding of one unit
1084 // will make the child item (which has no bullet) look like a sibling of its
1085 // parent. Therefore increase the indentation by one more unit to show that it
1086 // is in fact a child item (with no missing bullet) and not a sibling.
1087 int padding =
1088 views::kRelatedControlHorizontalSpacing * (parent_bulleted ? 2 : 1);
1089 column_set->AddPaddingColumn(0, padding);
1090 column_set->AddColumn(views::GridLayout::LEADING,
1091 views::GridLayout::LEADING,
1093 views::GridLayout::FIXED,
1094 horizontal_space - padding,
1098 void ExpandableContainerView::DetailsView::AddDetail(
1099 const base::string16& detail) {
1100 layout_->StartRowWithPadding(0, 0,
1101 0, views::kRelatedControlSmallVerticalSpacing);
1102 views::Label* detail_label =
1103 new views::Label(PrepareForDisplay(detail, false));
1104 detail_label->SetMultiLine(true);
1105 detail_label->SetHorizontalAlignment(gfx::ALIGN_LEFT);
1106 if (lighter_color_) {
1107 detail_label->SetEnabledColor(kLighterLabelColor);
1108 detail_label->SetAutoColorReadabilityEnabled(false);
1110 layout_->AddView(detail_label);
1113 gfx::Size ExpandableContainerView::DetailsView::GetPreferredSize() const {
1114 gfx::Size size = views::View::GetPreferredSize();
1115 return gfx::Size(size.width(), size.height() * state_);
1118 void ExpandableContainerView::DetailsView::AnimateToState(double state) {
1119 state_ = state;
1120 PreferredSizeChanged();
1121 SchedulePaint();
1124 // ExpandableContainerView -----------------------------------------------------
1126 ExpandableContainerView::ExpandableContainerView(
1127 ExtensionInstallDialogView* owner,
1128 const base::string16& description,
1129 const PermissionDetails& details,
1130 int horizontal_space,
1131 bool parent_bulleted,
1132 bool show_expand_link,
1133 bool lighter_color_details)
1134 : owner_(owner),
1135 details_view_(NULL),
1136 more_details_(NULL),
1137 slide_animation_(this),
1138 arrow_toggle_(NULL),
1139 expanded_(false) {
1140 views::GridLayout* layout = new views::GridLayout(this);
1141 SetLayoutManager(layout);
1142 int column_set_id = 0;
1143 views::ColumnSet* column_set = layout->AddColumnSet(column_set_id);
1144 column_set->AddColumn(views::GridLayout::LEADING,
1145 views::GridLayout::LEADING,
1147 views::GridLayout::USE_PREF,
1150 if (!description.empty()) {
1151 layout->StartRow(0, column_set_id);
1153 views::Label* description_label = new views::Label(description);
1154 description_label->SetMultiLine(true);
1155 description_label->SetHorizontalAlignment(gfx::ALIGN_LEFT);
1156 description_label->SizeToFit(horizontal_space);
1157 layout->AddView(new BulletedView(description_label));
1160 if (details.empty())
1161 return;
1163 details_view_ = new DetailsView(horizontal_space, parent_bulleted,
1164 lighter_color_details);
1166 layout->StartRow(0, column_set_id);
1167 layout->AddView(details_view_);
1169 for (size_t i = 0; i < details.size(); ++i)
1170 details_view_->AddDetail(details[i]);
1172 // TODO(meacer): Remove show_expand_link when the experiment is completed.
1173 if (show_expand_link) {
1174 views::Link* link = new views::Link(
1175 l10n_util::GetStringUTF16(IDS_EXTENSIONS_SHOW_DETAILS));
1177 // Make sure the link width column is as wide as needed for both Show and
1178 // Hide details, so that the arrow doesn't shift horizontally when we
1179 // toggle.
1180 int link_col_width =
1181 views::kRelatedControlHorizontalSpacing +
1182 std::max(gfx::GetStringWidth(
1183 l10n_util::GetStringUTF16(IDS_EXTENSIONS_HIDE_DETAILS),
1184 link->font_list()),
1185 gfx::GetStringWidth(
1186 l10n_util::GetStringUTF16(IDS_EXTENSIONS_SHOW_DETAILS),
1187 link->font_list()));
1189 column_set = layout->AddColumnSet(++column_set_id);
1190 // Padding to the left of the More Details column. If the parent is using
1191 // bullets for its items, then a padding of one unit will make the child
1192 // item (which has no bullet) look like a sibling of its parent. Therefore
1193 // increase the indentation by one more unit to show that it is in fact a
1194 // child item (with no missing bullet) and not a sibling.
1195 column_set->AddPaddingColumn(
1196 0, views::kRelatedControlHorizontalSpacing * (parent_bulleted ? 2 : 1));
1197 // The More Details column.
1198 column_set->AddColumn(views::GridLayout::LEADING,
1199 views::GridLayout::LEADING,
1201 views::GridLayout::FIXED,
1202 link_col_width,
1203 link_col_width);
1204 // The Up/Down arrow column.
1205 column_set->AddColumn(views::GridLayout::LEADING,
1206 views::GridLayout::LEADING,
1208 views::GridLayout::USE_PREF,
1212 // Add the More Details link.
1213 layout->StartRow(0, column_set_id);
1214 more_details_ = link;
1215 more_details_->set_listener(this);
1216 more_details_->SetHorizontalAlignment(gfx::ALIGN_LEFT);
1217 layout->AddView(more_details_);
1219 // Add the arrow after the More Details link.
1220 ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();
1221 arrow_toggle_ = new views::ImageButton(this);
1222 arrow_toggle_->SetImage(views::Button::STATE_NORMAL,
1223 rb.GetImageSkiaNamed(IDR_DOWN_ARROW));
1224 layout->AddView(arrow_toggle_);
1228 ExpandableContainerView::~ExpandableContainerView() {
1231 void ExpandableContainerView::ButtonPressed(
1232 views::Button* sender, const ui::Event& event) {
1233 ToggleDetailLevel();
1236 void ExpandableContainerView::LinkClicked(
1237 views::Link* source, int event_flags) {
1238 ToggleDetailLevel();
1241 void ExpandableContainerView::AnimationProgressed(
1242 const gfx::Animation* animation) {
1243 DCHECK_EQ(&slide_animation_, animation);
1244 if (details_view_)
1245 details_view_->AnimateToState(animation->GetCurrentValue());
1248 void ExpandableContainerView::AnimationEnded(const gfx::Animation* animation) {
1249 if (arrow_toggle_) {
1250 if (animation->GetCurrentValue() != 0.0) {
1251 arrow_toggle_->SetImage(
1252 views::Button::STATE_NORMAL,
1253 ui::ResourceBundle::GetSharedInstance().GetImageSkiaNamed(
1254 IDR_UP_ARROW));
1255 } else {
1256 arrow_toggle_->SetImage(
1257 views::Button::STATE_NORMAL,
1258 ui::ResourceBundle::GetSharedInstance().GetImageSkiaNamed(
1259 IDR_DOWN_ARROW));
1262 if (more_details_) {
1263 more_details_->SetText(expanded_ ?
1264 l10n_util::GetStringUTF16(IDS_EXTENSIONS_HIDE_DETAILS) :
1265 l10n_util::GetStringUTF16(IDS_EXTENSIONS_SHOW_DETAILS));
1269 void ExpandableContainerView::ChildPreferredSizeChanged(views::View* child) {
1270 owner_->ContentsChanged();
1273 void ExpandableContainerView::ToggleDetailLevel() {
1274 expanded_ = !expanded_;
1276 if (slide_animation_.IsShowing())
1277 slide_animation_.Hide();
1278 else
1279 slide_animation_.Show();
1282 void ExpandableContainerView::ExpandWithoutAnimation() {
1283 expanded_ = true;
1284 details_view_->AnimateToState(1.0);