Upstream version 9.38.198.0
[platform/framework/web/crosswalk.git] / src / chrome / browser / ui / views / extensions / extension_install_dialog_view.cc
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.
4
5 #include "chrome/browser/ui/views/extensions/extension_install_dialog_view.h"
6
7 #include <vector>
8
9 #include "base/basictypes.h"
10 #include "base/command_line.h"
11 #include "base/compiler_specific.h"
12 #include "base/i18n/rtl.h"
13 #include "base/metrics/histogram.h"
14 #include "base/strings/string_util.h"
15 #include "base/strings/utf_string_conversions.h"
16 #include "chrome/browser/browser_process.h"
17 #include "chrome/browser/extensions/api/experience_sampling_private/experience_sampling.h"
18 #include "chrome/browser/extensions/bundle_installer.h"
19 #include "chrome/browser/extensions/extension_install_prompt_experiment.h"
20 #include "chrome/browser/profiles/profile.h"
21 #include "chrome/browser/profiles/profile_manager.h"
22 #include "chrome/browser/ui/views/constrained_window_views.h"
23 #include "chrome/common/chrome_switches.h"
24 #include "chrome/common/extensions/extension_constants.h"
25 #include "chrome/installer/util/browser_distribution.h"
26 #include "content/public/browser/page_navigator.h"
27 #include "content/public/browser/web_contents.h"
28 #include "extensions/common/extension.h"
29 #include "grit/chromium_strings.h"
30 #include "grit/generated_resources.h"
31 #include "grit/google_chrome_strings.h"
32 #include "grit/theme_resources.h"
33 #include "ui/base/l10n/l10n_util.h"
34 #include "ui/base/resource/resource_bundle.h"
35 #include "ui/gfx/text_utils.h"
36 #include "ui/views/background.h"
37 #include "ui/views/border.h"
38 #include "ui/views/controls/button/checkbox.h"
39 #include "ui/views/controls/button/image_button.h"
40 #include "ui/views/controls/button/label_button.h"
41 #include "ui/views/controls/image_view.h"
42 #include "ui/views/controls/label.h"
43 #include "ui/views/controls/link.h"
44 #include "ui/views/controls/scroll_view.h"
45 #include "ui/views/controls/separator.h"
46 #include "ui/views/layout/box_layout.h"
47 #include "ui/views/layout/grid_layout.h"
48 #include "ui/views/layout/layout_constants.h"
49 #include "ui/views/widget/widget.h"
50 #include "ui/views/window/dialog_client_view.h"
51
52 using content::OpenURLParams;
53 using content::Referrer;
54 using extensions::BundleInstaller;
55 using extensions::ExperienceSamplingEvent;
56
57 namespace {
58
59 // Width of the bullet column in BulletedView.
60 const int kBulletWidth = 20;
61
62 // Size of extension icon in top left of dialog.
63 const int kIconSize = 64;
64
65 // We offset the icon a little bit from the right edge of the dialog, to make it
66 // align with the button below it.
67 const int kIconOffset = 16;
68
69 // The dialog will resize based on its content, but this sets a maximum height
70 // before overflowing a scrollbar.
71 const int kDialogMaxHeight = 300;
72
73 // Width of the left column of the dialog when the extension requests
74 // permissions.
75 const int kPermissionsLeftColumnWidth = 250;
76
77 // Width of the left column of the dialog when the extension requests no
78 // permissions.
79 const int kNoPermissionsLeftColumnWidth = 200;
80
81 // Width of the left column for bundle install prompts. There's only one column
82 // in this case, so make it wider than normal.
83 const int kBundleLeftColumnWidth = 300;
84
85 // Width of the left column for external install prompts. The text is long in
86 // this case, so make it wider than normal.
87 const int kExternalInstallLeftColumnWidth = 350;
88
89 // Lighter color for labels.
90 const SkColor kLighterLabelColor = SkColorSetRGB(0x99, 0x99, 0x99);
91
92 // Represents an action on a clickable link created by the install prompt
93 // experiment. This is used to group the actions in UMA histograms named
94 // Extensions.InstallPromptExperiment.ShowDetails and
95 // Extensions.InstallPromptExperiment.ShowPermissions.
96 enum ExperimentLinkAction {
97   LINK_SHOWN = 0,
98   LINK_NOT_SHOWN,
99   LINK_CLICKED,
100   NUM_LINK_ACTIONS
101 };
102
103 void AddResourceIcon(const gfx::ImageSkia* skia_image, void* data) {
104   views::View* parent = static_cast<views::View*>(data);
105   views::ImageView* image_view = new views::ImageView();
106   image_view->SetImage(*skia_image);
107   parent->AddChildView(image_view);
108 }
109
110 // Creates a string for displaying |message| to the user. If it has to look
111 // like a entry in a bullet point list, one is added.
112 base::string16 PrepareForDisplay(const base::string16& message,
113                                  bool bullet_point) {
114   return bullet_point ? l10n_util::GetStringFUTF16(
115       IDS_EXTENSION_PERMISSION_LINE,
116       message) : message;
117 }
118
119 }  // namespace
120
121 BulletedView::BulletedView(views::View* view) {
122   views::GridLayout* layout = new views::GridLayout(this);
123   SetLayoutManager(layout);
124   views::ColumnSet* column_set = layout->AddColumnSet(0);
125   column_set->AddColumn(views::GridLayout::CENTER,
126                         views::GridLayout::LEADING,
127                         0,
128                         views::GridLayout::FIXED,
129                         kBulletWidth,
130                         0);
131   column_set->AddColumn(views::GridLayout::LEADING,
132                         views::GridLayout::LEADING,
133                         0,
134                         views::GridLayout::USE_PREF,
135                         0,  // No fixed width.
136                         0);
137   layout->StartRow(0, 0);
138   layout->AddView(new views::Label(PrepareForDisplay(base::string16(), true)));
139   layout->AddView(view);
140 }
141
142 CheckboxedView::CheckboxedView(views::View* view,
143                                views::ButtonListener* listener) {
144   views::GridLayout* layout = new views::GridLayout(this);
145   SetLayoutManager(layout);
146   views::ColumnSet* column_set = layout->AddColumnSet(0);
147   column_set->AddColumn(views::GridLayout::LEADING,
148                         views::GridLayout::LEADING,
149                         0,
150                         views::GridLayout::USE_PREF,
151                         0,  // No fixed width.
152                         0);
153    column_set->AddColumn(views::GridLayout::LEADING,
154                          views::GridLayout::LEADING,
155                          0,
156                          views::GridLayout::USE_PREF,
157                          0,  // No fixed width.
158                          0);
159   layout->StartRow(0, 0);
160   views::Checkbox* checkbox = new views::Checkbox(base::string16());
161   checkbox->set_listener(listener);
162   // Alignment needs to be explicitly set again here, otherwise the views are
163   // not vertically centered.
164   layout->AddView(checkbox, 1, 1,
165                   views::GridLayout::LEADING, views::GridLayout::CENTER);
166   layout->AddView(view, 1, 1,
167                   views::GridLayout::LEADING, views::GridLayout::CENTER);
168 }
169
170 void ShowExtensionInstallDialogImpl(
171     const ExtensionInstallPrompt::ShowParams& show_params,
172     ExtensionInstallPrompt::Delegate* delegate,
173     scoped_refptr<ExtensionInstallPrompt::Prompt> prompt) {
174   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
175   CreateBrowserModalDialogViews(
176       new ExtensionInstallDialogView(show_params.navigator, delegate, prompt),
177       show_params.parent_window)->Show();
178 }
179
180 CustomScrollableView::CustomScrollableView() {}
181 CustomScrollableView::~CustomScrollableView() {}
182
183 void CustomScrollableView::Layout() {
184   SetBounds(x(), y(), width(), GetHeightForWidth(width()));
185   views::View::Layout();
186 }
187
188 ExtensionInstallDialogView::ExtensionInstallDialogView(
189     content::PageNavigator* navigator,
190     ExtensionInstallPrompt::Delegate* delegate,
191     scoped_refptr<ExtensionInstallPrompt::Prompt> prompt)
192     : navigator_(navigator),
193       delegate_(delegate),
194       prompt_(prompt),
195       scroll_view_(NULL),
196       scrollable_(NULL),
197       scrollable_header_only_(NULL),
198       show_details_link_(NULL),
199       checkbox_info_label_(NULL),
200       unchecked_boxes_(0) {
201   // Possible grid layouts without ExtensionPermissionDialog experiment:
202   // Inline install
203   //      w/ permissions                 no permissions
204   // +--------------------+------+  +--------------+------+
205   // | heading            | icon |  | heading      | icon |
206   // +--------------------|      |  +--------------|      |
207   // | rating             |      |  | rating       |      |
208   // +--------------------|      |  +--------------+      |
209   // | user_count         |      |  | user_count   |      |
210   // +--------------------|      |  +--------------|      |
211   // | store_link         |      |  | store_link   |      |
212   // +--------------------+------+  +--------------+------+
213   // |      separator            |
214   // +--------------------+------+
215   // | permissions_header |      |
216   // +--------------------+------+
217   // | permission1        |      |
218   // +--------------------+------+
219   // | permission2        |      |
220   // +--------------------+------+
221   //
222   // Regular install
223   // w/ permissions                     no permissions
224   // +--------------------+------+  +--------------+------+
225   // | heading            | icon |  | heading      | icon |
226   // +--------------------|      |  +--------------+------+
227   // | permissions_header |      |
228   // +--------------------|      |
229   // | permission1        |      |
230   // +--------------------|      |
231   // | permission2        |      |
232   // +--------------------+------+
233   //
234   // If the ExtensionPermissionDialog is on, the layout is modified depending
235   // on the experiment group. For text only experiment, a footer is added at the
236   // bottom of the layouts. For others, inline details are added below some of
237   // the permissions.
238   //
239   // Regular install w/ permissions and footer (experiment):
240   // +--------------------+------+
241   // | heading            | icon |
242   // +--------------------|      |
243   // | permissions_header |      |
244   // +--------------------|      |
245   // | permission1        |      |
246   // +--------------------|      |
247   // | permission2        |      |
248   // +--------------------+------+
249   // | footer text        |      |
250   // +--------------------+------+
251   //
252   // Regular install w/ permissions and inline explanations (experiment):
253   // +--------------------+------+
254   // | heading            | icon |
255   // +--------------------|      |
256   // | permissions_header |      |
257   // +--------------------|      |
258   // | permission1        |      |
259   // +--------------------|      |
260   // | explanation1       |      |
261   // +--------------------|      |
262   // | permission2        |      |
263   // +--------------------|      |
264   // | explanation2       |      |
265   // +--------------------+------+
266   //
267   // Regular install w/ permissions and inline explanations (experiment):
268   // +--------------------+------+
269   // | heading            | icon |
270   // +--------------------|      |
271   // | permissions_header |      |
272   // +--------------------|      |
273   // |checkbox|permission1|      |
274   // +--------------------|      |
275   // |checkbox|permission2|      |
276   // +--------------------+------+
277   //
278   // Additionally, links or informational text is added to non-client areas of
279   // the dialog depending on the experiment group.
280
281   int left_column_width =
282       (prompt->ShouldShowPermissions() + prompt->GetRetainedFileCount()) > 0
283           ? kPermissionsLeftColumnWidth
284           : kNoPermissionsLeftColumnWidth;
285   if (is_bundle_install())
286     left_column_width = kBundleLeftColumnWidth;
287   if (is_external_install())
288     left_column_width = kExternalInstallLeftColumnWidth;
289
290   scroll_view_ = new views::ScrollView();
291   scroll_view_->set_hide_horizontal_scrollbar(true);
292   AddChildView(scroll_view_);
293
294   int column_set_id = 0;
295   // Create the full scrollable view which will contain all the information
296   // including the permissions.
297   scrollable_ = new CustomScrollableView();
298   views::GridLayout* layout = CreateLayout(
299       scrollable_, left_column_width, column_set_id, false);
300   ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();
301
302   if (prompt->ShouldShowPermissions() &&
303       prompt->experiment()->should_show_expandable_permission_list()) {
304     // If the experiment should hide the permission list initially, create a
305     // simple layout that contains only the header, extension name and icon.
306     scrollable_header_only_ = new CustomScrollableView();
307     CreateLayout(scrollable_header_only_, left_column_width,
308                  column_set_id, true);
309     scroll_view_->SetContents(scrollable_header_only_);
310   } else {
311     scroll_view_->SetContents(scrollable_);
312   }
313
314   int dialog_width = left_column_width + 2 * views::kPanelHorizMargin;
315   if (!is_bundle_install())
316     dialog_width += views::kPanelHorizMargin + kIconSize + kIconOffset;
317
318   // Widen the dialog for experiment with checkboxes so that the information
319   // label fits the area to the left of the buttons.
320   if (prompt->experiment()->show_checkboxes())
321     dialog_width += 4 * views::kPanelHorizMargin;
322
323   if (prompt->has_webstore_data()) {
324     layout->StartRow(0, column_set_id);
325     views::View* rating = new views::View();
326     rating->SetLayoutManager(new views::BoxLayout(
327         views::BoxLayout::kHorizontal, 0, 0, 0));
328     layout->AddView(rating);
329     prompt->AppendRatingStars(AddResourceIcon, rating);
330
331     const gfx::FontList& small_font_list =
332         rb.GetFontList(ui::ResourceBundle::SmallFont);
333     views::Label* rating_count =
334         new views::Label(prompt->GetRatingCount(), small_font_list);
335     // Add some space between the stars and the rating count.
336     rating_count->SetBorder(views::Border::CreateEmptyBorder(0, 2, 0, 0));
337     rating->AddChildView(rating_count);
338
339     layout->StartRow(0, column_set_id);
340     views::Label* user_count =
341         new views::Label(prompt->GetUserCount(), small_font_list);
342     user_count->SetAutoColorReadabilityEnabled(false);
343     user_count->SetEnabledColor(SK_ColorGRAY);
344     layout->AddView(user_count);
345
346     layout->StartRow(0, column_set_id);
347     views::Link* store_link = new views::Link(
348         l10n_util::GetStringUTF16(IDS_EXTENSION_PROMPT_STORE_LINK));
349     store_link->SetFontList(small_font_list);
350     store_link->set_listener(this);
351     layout->AddView(store_link);
352   }
353
354   if (is_bundle_install()) {
355     BundleInstaller::ItemList items = prompt->bundle()->GetItemsWithState(
356         BundleInstaller::Item::STATE_PENDING);
357     for (size_t i = 0; i < items.size(); ++i) {
358       base::string16 extension_name =
359           base::UTF8ToUTF16(items[i].localized_name);
360       base::i18n::AdjustStringForLocaleDirection(&extension_name);
361       layout->AddPaddingRow(0, views::kRelatedControlVerticalSpacing);
362       layout->StartRow(0, column_set_id);
363       views::Label* extension_label = new views::Label(
364           PrepareForDisplay(extension_name, true));
365       extension_label->SetMultiLine(true);
366       extension_label->SetHorizontalAlignment(gfx::ALIGN_LEFT);
367       extension_label->SizeToFit(left_column_width);
368       layout->AddView(extension_label);
369     }
370   }
371
372   if (prompt->ShouldShowPermissions()) {
373     layout->AddPaddingRow(0, views::kRelatedControlVerticalSpacing);
374
375     if (prompt->GetPermissionCount() > 0) {
376       if (is_inline_install()) {
377         layout->StartRow(0, column_set_id);
378         layout->AddView(new views::Separator(views::Separator::HORIZONTAL),
379                         3, 1, views::GridLayout::FILL, views::GridLayout::FILL);
380         layout->AddPaddingRow(0, views::kRelatedControlVerticalSpacing);
381       }
382
383       layout->StartRow(0, column_set_id);
384       views::Label* permissions_header = NULL;
385       if (is_bundle_install()) {
386         // We need to pass the FontList in the constructor, rather than calling
387         // SetFontList later, because otherwise SizeToFit mis-judges the width
388         // of the line.
389         permissions_header =
390             new views::Label(prompt->GetPermissionsHeading(),
391                              rb.GetFontList(ui::ResourceBundle::MediumFont));
392       } else {
393         permissions_header = new views::Label(prompt->GetPermissionsHeading());
394       }
395       permissions_header->SetMultiLine(true);
396       permissions_header->SetHorizontalAlignment(gfx::ALIGN_LEFT);
397       permissions_header->SizeToFit(left_column_width);
398       layout->AddView(permissions_header);
399
400       for (size_t i = 0; i < prompt->GetPermissionCount(); ++i) {
401         layout->AddPaddingRow(0, views::kRelatedControlVerticalSpacing);
402         layout->StartRow(0, column_set_id);
403         views::Label* permission_label =
404             new views::Label(prompt->GetPermission(i));
405
406         const SkColor kTextHighlight = SK_ColorRED;
407         const SkColor kBackgroundHighlight = SkColorSetRGB(0xFB, 0xF7, 0xA3);
408         if (prompt->experiment()->ShouldHighlightText(
409                 prompt->GetPermission(i))) {
410           permission_label->SetAutoColorReadabilityEnabled(false);
411           permission_label->SetEnabledColor(kTextHighlight);
412         } else if (prompt->experiment()->ShouldHighlightBackground(
413                        prompt->GetPermission(i))) {
414           permission_label->SetLineHeight(18);
415           permission_label->set_background(
416               views::Background::CreateSolidBackground(kBackgroundHighlight));
417         }
418
419         permission_label->SetMultiLine(true);
420         permission_label->SetHorizontalAlignment(gfx::ALIGN_LEFT);
421
422         if (prompt->experiment()->show_checkboxes()) {
423           permission_label->SizeToFit(left_column_width);
424           layout->AddView(new CheckboxedView(permission_label, this));
425           ++unchecked_boxes_;
426         } else {
427           permission_label->SizeToFit(left_column_width - kBulletWidth);
428           layout->AddView(new BulletedView(permission_label));
429         }
430
431         // If we have more details to provide, show them in collapsed form.
432         if (!prompt->GetPermissionsDetails(i).empty()) {
433           layout->StartRow(0, column_set_id);
434           PermissionDetails details;
435           details.push_back(
436               PrepareForDisplay(prompt->GetPermissionsDetails(i), false));
437           ExpandableContainerView* details_container =
438               new ExpandableContainerView(
439                   this, base::string16(), details, left_column_width,
440                   true, true, false);
441           layout->AddView(details_container);
442         }
443
444         if (prompt->experiment()->should_show_inline_explanations()) {
445           base::string16 explanation =
446               prompt->experiment()->GetInlineExplanation(
447                   prompt->GetPermission(i));
448           if (!explanation.empty()) {
449             PermissionDetails details;
450             details.push_back(explanation);
451             ExpandableContainerView* container =
452                 new ExpandableContainerView(this, base::string16(), details,
453                                             left_column_width,
454                                             false, false, true);
455             // Inline explanations are expanded by default if there is
456             // no "Show details" link.
457             if (!prompt->experiment()->show_details_link())
458               container->ExpandWithoutAnimation();
459             layout->StartRow(0, column_set_id);
460             layout->AddView(container);
461             inline_explanations_.push_back(container);
462           }
463         }
464       }
465     } else {
466       layout->AddPaddingRow(0, views::kRelatedControlVerticalSpacing);
467       layout->StartRow(0, column_set_id);
468       views::Label* permission_label = new views::Label(
469           l10n_util::GetStringUTF16(IDS_EXTENSION_NO_SPECIAL_PERMISSIONS));
470       permission_label->SetMultiLine(true);
471       permission_label->SetHorizontalAlignment(gfx::ALIGN_LEFT);
472       permission_label->SizeToFit(left_column_width);
473       layout->AddView(permission_label);
474     }
475   }
476
477   if (prompt->GetRetainedFileCount()) {
478     // Slide in under the permissions, if there are any. If there are
479     // either, the retained files prompt stretches all the way to the
480     // right of the dialog. If there are no permissions, the retained
481     // files prompt just takes up the left column.
482     int space_for_files = left_column_width;
483     if (prompt->GetPermissionCount()) {
484       space_for_files += kIconSize;
485       views::ColumnSet* column_set = layout->AddColumnSet(++column_set_id);
486       column_set->AddColumn(views::GridLayout::FILL,
487                             views::GridLayout::FILL,
488                             1,
489                             views::GridLayout::USE_PREF,
490                             0,  // no fixed width
491                             space_for_files);
492     }
493
494     layout->AddPaddingRow(0, views::kRelatedControlVerticalSpacing);
495
496     layout->StartRow(0, column_set_id);
497     views::Label* retained_files_header = NULL;
498     retained_files_header = new views::Label(prompt->GetRetainedFilesHeading());
499     retained_files_header->SetMultiLine(true);
500     retained_files_header->SetHorizontalAlignment(gfx::ALIGN_LEFT);
501     retained_files_header->SizeToFit(space_for_files);
502     layout->AddView(retained_files_header);
503
504     layout->StartRow(0, column_set_id);
505     PermissionDetails details;
506     for (size_t i = 0; i < prompt->GetRetainedFileCount(); ++i)
507       details.push_back(prompt->GetRetainedFile(i));
508     ExpandableContainerView* issue_advice_view =
509         new ExpandableContainerView(
510             this, base::string16(), details, space_for_files,
511             false, true, false);
512     layout->AddView(issue_advice_view);
513   }
514
515   DCHECK(prompt->type() >= 0);
516   UMA_HISTOGRAM_ENUMERATION("Extensions.InstallPrompt.Type",
517                             prompt->type(),
518                             ExtensionInstallPrompt::NUM_PROMPT_TYPES);
519
520   if (prompt->ShouldShowPermissions()) {
521     if (prompt->ShouldShowExplanationText()) {
522       views::ColumnSet* column_set = layout->AddColumnSet(++column_set_id);
523       column_set->AddColumn(views::GridLayout::LEADING,
524                             views::GridLayout::FILL,
525                             1,
526                             views::GridLayout::USE_PREF,
527                             0,
528                             0);
529       // Add two rows of space so that the text stands out.
530       layout->AddPaddingRow(0, 2 * views::kRelatedControlVerticalSpacing);
531
532       layout->StartRow(0, column_set_id);
533       views::Label* explanation =
534           new views::Label(prompt->experiment()->GetExplanationText());
535       explanation->SetMultiLine(true);
536       explanation->SetHorizontalAlignment(gfx::ALIGN_LEFT);
537       explanation->SizeToFit(left_column_width + kIconSize);
538       layout->AddView(explanation);
539     }
540
541     if (prompt->experiment()->should_show_expandable_permission_list() ||
542         (prompt->experiment()->show_details_link() &&
543          prompt->experiment()->should_show_inline_explanations() &&
544          !inline_explanations_.empty())) {
545       // Don't show the "Show details" link if there are retained
546       // files.  These have their own "Show details" links and having
547       // multiple levels of links is confusing.
548       if (prompt->GetRetainedFileCount() == 0) {
549         int text_id =
550             prompt->experiment()->should_show_expandable_permission_list()
551                 ? IDS_EXTENSION_PROMPT_EXPERIMENT_SHOW_PERMISSIONS
552                 : IDS_EXTENSION_PROMPT_EXPERIMENT_SHOW_DETAILS;
553         show_details_link_ = new views::Link(
554             l10n_util::GetStringUTF16(text_id));
555         show_details_link_->SetHorizontalAlignment(gfx::ALIGN_LEFT);
556         show_details_link_->set_listener(this);
557         UpdateLinkActionHistogram(LINK_SHOWN);
558       } else {
559         UpdateLinkActionHistogram(LINK_NOT_SHOWN);
560       }
561     }
562
563     if (prompt->experiment()->show_checkboxes()) {
564       checkbox_info_label_ = new views::Label(
565           l10n_util::GetStringUTF16(
566               IDS_EXTENSION_PROMPT_EXPERIMENT_CHECKBOX_INFO));
567       checkbox_info_label_->SetMultiLine(true);
568       checkbox_info_label_->SetHorizontalAlignment(gfx::ALIGN_LEFT);
569       checkbox_info_label_->SetAutoColorReadabilityEnabled(false);
570       checkbox_info_label_->SetEnabledColor(kLighterLabelColor);
571     }
572   }
573
574   gfx::Size scrollable_size = scrollable_->GetPreferredSize();
575   scrollable_->SetBoundsRect(gfx::Rect(scrollable_size));
576   dialog_size_ = gfx::Size(
577       dialog_width,
578       std::min(scrollable_size.height(), kDialogMaxHeight));
579
580   if (scrollable_header_only_) {
581     gfx::Size header_only_size = scrollable_header_only_->GetPreferredSize();
582     scrollable_header_only_->SetBoundsRect(gfx::Rect(header_only_size));
583     dialog_size_ = gfx::Size(
584         dialog_width, std::min(header_only_size.height(), kDialogMaxHeight));
585   }
586
587   std::string event_name = ExperienceSamplingEvent::kExtensionInstallDialog;
588   event_name.append(
589       ExtensionInstallPrompt::PromptTypeToString(prompt_->type()));
590   sampling_event_ = ExperienceSamplingEvent::Create(event_name);
591 }
592
593 ExtensionInstallDialogView::~ExtensionInstallDialogView() {}
594
595 views::GridLayout* ExtensionInstallDialogView::CreateLayout(
596     views::View* parent,
597     int left_column_width,
598     int column_set_id,
599     bool single_detail_row) const {
600   views::GridLayout* layout = views::GridLayout::CreatePanel(parent);
601   parent->SetLayoutManager(layout);
602
603   views::ColumnSet* column_set = layout->AddColumnSet(column_set_id);
604   column_set->AddColumn(views::GridLayout::LEADING,
605                         views::GridLayout::FILL,
606                         0,  // no resizing
607                         views::GridLayout::USE_PREF,
608                         0,  // no fixed width
609                         left_column_width);
610   if (!is_bundle_install()) {
611     column_set->AddPaddingColumn(0, views::kPanelHorizMargin);
612     column_set->AddColumn(views::GridLayout::TRAILING,
613                           views::GridLayout::LEADING,
614                           0,  // no resizing
615                           views::GridLayout::USE_PREF,
616                           0,  // no fixed width
617                           kIconSize);
618   }
619
620   layout->StartRow(0, column_set_id);
621
622   ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();
623
624   views::Label* heading = new views::Label(
625       prompt_->GetHeading(), rb.GetFontList(ui::ResourceBundle::MediumFont));
626   heading->SetMultiLine(true);
627   heading->SetHorizontalAlignment(gfx::ALIGN_LEFT);
628   heading->SizeToFit(left_column_width);
629   layout->AddView(heading);
630
631   if (!is_bundle_install()) {
632     // Scale down to icon size, but allow smaller icons (don't scale up).
633     const gfx::ImageSkia* image = prompt_->icon().ToImageSkia();
634     gfx::Size size(image->width(), image->height());
635     if (size.width() > kIconSize || size.height() > kIconSize)
636       size = gfx::Size(kIconSize, kIconSize);
637     views::ImageView* icon = new views::ImageView();
638     icon->SetImageSize(size);
639     icon->SetImage(*image);
640     icon->SetHorizontalAlignment(views::ImageView::CENTER);
641     icon->SetVerticalAlignment(views::ImageView::CENTER);
642     if (single_detail_row) {
643       layout->AddView(icon);
644     } else {
645       int icon_row_span = 1;
646       if (is_inline_install()) {
647         // Also span the rating, user_count and store_link rows.
648         icon_row_span = 4;
649       } else if (prompt_->ShouldShowPermissions()) {
650         size_t permission_count = prompt_->GetPermissionCount();
651         // Also span the permission header and each of the permission rows (all
652         // have a padding row above it). This also works for the 'no special
653         // permissions' case.
654         icon_row_span = 3 + permission_count * 2;
655       } else if (prompt_->GetRetainedFileCount()) {
656         // Also span the permission header and the retained files container.
657         icon_row_span = 4;
658       }
659       layout->AddView(icon, 1, icon_row_span);
660     }
661   }
662   return layout;
663 }
664
665 void ExtensionInstallDialogView::ContentsChanged() {
666   Layout();
667 }
668
669 void ExtensionInstallDialogView::ViewHierarchyChanged(
670     const ViewHierarchyChangedDetails& details) {
671   // Since we want the links to show up in the same visual row as the accept
672   // and cancel buttons, which is provided by the framework, we must add the
673   // buttons to the non-client view, which is the parent of this view.
674   // Similarly, when we're removed from the view hierarchy, we must take care
675   // to clean up those items as well.
676   if (details.child == this) {
677     if (details.is_add) {
678       if (show_details_link_)
679         details.parent->AddChildView(show_details_link_);
680       if (checkbox_info_label_)
681         details.parent->AddChildView(checkbox_info_label_);
682     } else {
683       if (show_details_link_)
684         details.parent->RemoveChildView(show_details_link_);
685       if (checkbox_info_label_)
686         details.parent->RemoveChildView(checkbox_info_label_);
687     }
688   }
689 }
690
691 int ExtensionInstallDialogView::GetDialogButtons() const {
692   int buttons = prompt_->GetDialogButtons();
693   // Simply having just an OK button is *not* supported. See comment on function
694   // GetDialogButtons in dialog_delegate.h for reasons.
695   DCHECK_GT(buttons & ui::DIALOG_BUTTON_CANCEL, 0);
696   return buttons;
697 }
698
699 base::string16 ExtensionInstallDialogView::GetDialogButtonLabel(
700     ui::DialogButton button) const {
701   switch (button) {
702     case ui::DIALOG_BUTTON_OK:
703       return prompt_->GetAcceptButtonLabel();
704     case ui::DIALOG_BUTTON_CANCEL:
705       return prompt_->HasAbortButtonLabel()
706                  ? prompt_->GetAbortButtonLabel()
707                  : l10n_util::GetStringUTF16(IDS_CANCEL);
708     default:
709       NOTREACHED();
710       return base::string16();
711   }
712 }
713
714 int ExtensionInstallDialogView::GetDefaultDialogButton() const {
715   return ui::DIALOG_BUTTON_CANCEL;
716 }
717
718 bool ExtensionInstallDialogView::Cancel() {
719   UpdateInstallResultHistogram(false);
720   if (sampling_event_.get())
721     sampling_event_->CreateUserDecisionEvent(ExperienceSamplingEvent::kDeny);
722   delegate_->InstallUIAbort(true);
723   return true;
724 }
725
726 bool ExtensionInstallDialogView::Accept() {
727   UpdateInstallResultHistogram(true);
728   if (sampling_event_.get())
729     sampling_event_->CreateUserDecisionEvent(ExperienceSamplingEvent::kProceed);
730   delegate_->InstallUIProceed();
731   return true;
732 }
733
734 ui::ModalType ExtensionInstallDialogView::GetModalType() const {
735   return ui::MODAL_TYPE_WINDOW;
736 }
737
738 base::string16 ExtensionInstallDialogView::GetWindowTitle() const {
739   return prompt_->GetDialogTitle();
740 }
741
742 void ExtensionInstallDialogView::LinkClicked(views::Link* source,
743                                              int event_flags) {
744   if (source == show_details_link_) {
745     UpdateLinkActionHistogram(LINK_CLICKED);
746     // Show details link is used to either reveal whole permission list or to
747     // reveal inline explanations.
748     if (prompt_->experiment()->should_show_expandable_permission_list()) {
749       gfx::Rect bounds = GetWidget()->GetWindowBoundsInScreen();
750       int spacing = bounds.height() -
751           scrollable_header_only_->GetPreferredSize().height();
752       int content_height = std::min(scrollable_->GetPreferredSize().height(),
753                                     kDialogMaxHeight);
754       bounds.set_height(spacing + content_height);
755       scroll_view_->SetContents(scrollable_);
756       GetWidget()->SetBoundsConstrained(bounds);
757       ContentsChanged();
758     } else {
759       ToggleInlineExplanations();
760     }
761     show_details_link_->SetVisible(false);
762   } else {
763     GURL store_url(extension_urls::GetWebstoreItemDetailURLPrefix() +
764                    prompt_->extension()->id());
765     OpenURLParams params(
766         store_url, Referrer(), NEW_FOREGROUND_TAB,
767         content::PAGE_TRANSITION_LINK,
768         false);
769     navigator_->OpenURL(params);
770     GetWidget()->Close();
771   }
772 }
773
774 void ExtensionInstallDialogView::ToggleInlineExplanations() {
775   for (InlineExplanations::iterator it = inline_explanations_.begin();
776       it != inline_explanations_.end(); ++it)
777     (*it)->ToggleDetailLevel();
778 }
779
780 void ExtensionInstallDialogView::Layout() {
781   scroll_view_->SetBounds(0, 0, width(), height());
782
783   if (show_details_link_ || checkbox_info_label_) {
784     views::LabelButton* cancel_button = GetDialogClientView()->cancel_button();
785     gfx::Rect parent_bounds = parent()->GetContentsBounds();
786     // By default, layouts have an inset of kButtonHEdgeMarginNew. In order to
787     // align the link horizontally with the left side of the contents of the
788     // layout, put a horizontal margin with this amount.
789     const int horizontal_margin = views::kButtonHEdgeMarginNew;
790     const int vertical_margin = views::kButtonVEdgeMarginNew;
791     int y_buttons = parent_bounds.bottom() -
792         cancel_button->GetPreferredSize().height() - vertical_margin;
793     int max_width = dialog_size_.width() - cancel_button->width() * 2 -
794         horizontal_margin * 2 - views::kRelatedButtonHSpacing;
795     if (show_details_link_) {
796       gfx::Size link_size = show_details_link_->GetPreferredSize();
797       show_details_link_->SetBounds(
798           horizontal_margin,
799           y_buttons + (cancel_button->height() - link_size.height()) / 2,
800           link_size.width(), link_size.height());
801     }
802     if (checkbox_info_label_) {
803       gfx::Size label_size = checkbox_info_label_->GetPreferredSize();
804       checkbox_info_label_->SetBounds(
805           horizontal_margin,
806           y_buttons + (cancel_button->height() - label_size.height()) / 2,
807           label_size.width(), label_size.height());
808       checkbox_info_label_->SizeToFit(max_width);
809     }
810   }
811   // Disable accept button if there are unchecked boxes and
812   // the experiment is on.
813   if (prompt_->experiment()->show_checkboxes())
814     GetDialogClientView()->ok_button()->SetEnabled(unchecked_boxes_ == 0);
815
816   DialogDelegateView::Layout();
817 }
818
819 gfx::Size ExtensionInstallDialogView::GetPreferredSize() const {
820   return dialog_size_;
821 }
822
823 void ExtensionInstallDialogView::ButtonPressed(views::Button* sender,
824                                                const ui::Event& event) {
825   if (std::string(views::Checkbox::kViewClassName) == sender->GetClassName()) {
826     views::Checkbox* checkbox = static_cast<views::Checkbox*>(sender);
827     if (checkbox->checked())
828       --unchecked_boxes_;
829     else
830       ++unchecked_boxes_;
831
832     GetDialogClientView()->ok_button()->SetEnabled(unchecked_boxes_ == 0);
833     checkbox_info_label_->SetVisible(unchecked_boxes_ > 0);
834   }
835 }
836
837 void ExtensionInstallDialogView::UpdateInstallResultHistogram(bool accepted)
838     const {
839   if (prompt_->type() == ExtensionInstallPrompt::INSTALL_PROMPT)
840     UMA_HISTOGRAM_BOOLEAN("Extensions.InstallPrompt.Accepted", accepted);
841 }
842
843 void ExtensionInstallDialogView::UpdateLinkActionHistogram(int action_type)
844     const {
845   if (prompt_->experiment()->should_show_expandable_permission_list()) {
846     // The clickable link in the UI is "Show Permissions".
847     UMA_HISTOGRAM_ENUMERATION(
848         "Extensions.InstallPromptExperiment.ShowPermissions",
849         action_type,
850         NUM_LINK_ACTIONS);
851   } else {
852     // The clickable link in the UI is "Show Details".
853     UMA_HISTOGRAM_ENUMERATION(
854         "Extensions.InstallPromptExperiment.ShowDetails",
855         action_type,
856         NUM_LINK_ACTIONS);
857   }
858 }
859
860 // ExpandableContainerView::DetailsView ----------------------------------------
861
862 ExpandableContainerView::DetailsView::DetailsView(int horizontal_space,
863                                                   bool parent_bulleted,
864                                                   bool lighter_color)
865     : layout_(new views::GridLayout(this)),
866       state_(0),
867       lighter_color_(lighter_color) {
868   SetLayoutManager(layout_);
869   views::ColumnSet* column_set = layout_->AddColumnSet(0);
870   // If the parent is using bullets for its items, then a padding of one unit
871   // will make the child item (which has no bullet) look like a sibling of its
872   // parent. Therefore increase the indentation by one more unit to show that it
873   // is in fact a child item (with no missing bullet) and not a sibling.
874   int padding =
875       views::kRelatedControlHorizontalSpacing * (parent_bulleted ? 2 : 1);
876   column_set->AddPaddingColumn(0, padding);
877   column_set->AddColumn(views::GridLayout::LEADING,
878                         views::GridLayout::LEADING,
879                         0,
880                         views::GridLayout::FIXED,
881                         horizontal_space - padding,
882                         0);
883 }
884
885 void ExpandableContainerView::DetailsView::AddDetail(
886     const base::string16& detail) {
887   layout_->StartRowWithPadding(0, 0,
888                                0, views::kRelatedControlSmallVerticalSpacing);
889   views::Label* detail_label =
890       new views::Label(PrepareForDisplay(detail, false));
891   detail_label->SetMultiLine(true);
892   detail_label->SetHorizontalAlignment(gfx::ALIGN_LEFT);
893   if (lighter_color_) {
894     detail_label->SetEnabledColor(kLighterLabelColor);
895     detail_label->SetAutoColorReadabilityEnabled(false);
896   }
897   layout_->AddView(detail_label);
898 }
899
900 gfx::Size ExpandableContainerView::DetailsView::GetPreferredSize() const {
901   gfx::Size size = views::View::GetPreferredSize();
902   return gfx::Size(size.width(), size.height() * state_);
903 }
904
905 void ExpandableContainerView::DetailsView::AnimateToState(double state) {
906   state_ = state;
907   PreferredSizeChanged();
908   SchedulePaint();
909 }
910
911 // ExpandableContainerView -----------------------------------------------------
912
913 ExpandableContainerView::ExpandableContainerView(
914     ExtensionInstallDialogView* owner,
915     const base::string16& description,
916     const PermissionDetails& details,
917     int horizontal_space,
918     bool parent_bulleted,
919     bool show_expand_link,
920     bool lighter_color_details)
921     : owner_(owner),
922       details_view_(NULL),
923       more_details_(NULL),
924       slide_animation_(this),
925       arrow_toggle_(NULL),
926       expanded_(false) {
927   views::GridLayout* layout = new views::GridLayout(this);
928   SetLayoutManager(layout);
929   int column_set_id = 0;
930   views::ColumnSet* column_set = layout->AddColumnSet(column_set_id);
931   column_set->AddColumn(views::GridLayout::LEADING,
932                         views::GridLayout::LEADING,
933                         0,
934                         views::GridLayout::USE_PREF,
935                         0,
936                         0);
937   if (!description.empty()) {
938     layout->StartRow(0, column_set_id);
939
940     views::Label* description_label = new views::Label(description);
941     description_label->SetMultiLine(true);
942     description_label->SetHorizontalAlignment(gfx::ALIGN_LEFT);
943     description_label->SizeToFit(horizontal_space);
944     layout->AddView(new BulletedView(description_label));
945   }
946
947   if (details.empty())
948     return;
949
950   details_view_ = new DetailsView(horizontal_space, parent_bulleted,
951                                   lighter_color_details);
952
953   layout->StartRow(0, column_set_id);
954   layout->AddView(details_view_);
955
956   for (size_t i = 0; i < details.size(); ++i)
957     details_view_->AddDetail(details[i]);
958
959   // TODO(meacer): Remove show_expand_link when the experiment is completed.
960   if (show_expand_link) {
961     views::Link* link = new views::Link(
962         l10n_util::GetStringUTF16(IDS_EXTENSIONS_SHOW_DETAILS));
963
964     // Make sure the link width column is as wide as needed for both Show and
965     // Hide details, so that the arrow doesn't shift horizontally when we
966     // toggle.
967     int link_col_width =
968         views::kRelatedControlHorizontalSpacing +
969         std::max(gfx::GetStringWidth(
970                      l10n_util::GetStringUTF16(IDS_EXTENSIONS_HIDE_DETAILS),
971                      link->font_list()),
972                  gfx::GetStringWidth(
973                      l10n_util::GetStringUTF16(IDS_EXTENSIONS_SHOW_DETAILS),
974                      link->font_list()));
975
976     column_set = layout->AddColumnSet(++column_set_id);
977     // Padding to the left of the More Details column. If the parent is using
978     // bullets for its items, then a padding of one unit will make the child
979     // item (which has no bullet) look like a sibling of its parent. Therefore
980     // increase the indentation by one more unit to show that it is in fact a
981     // child item (with no missing bullet) and not a sibling.
982     column_set->AddPaddingColumn(
983         0, views::kRelatedControlHorizontalSpacing * (parent_bulleted ? 2 : 1));
984     // The More Details column.
985     column_set->AddColumn(views::GridLayout::LEADING,
986                           views::GridLayout::LEADING,
987                           0,
988                           views::GridLayout::FIXED,
989                           link_col_width,
990                           link_col_width);
991     // The Up/Down arrow column.
992     column_set->AddColumn(views::GridLayout::LEADING,
993                           views::GridLayout::LEADING,
994                           0,
995                           views::GridLayout::USE_PREF,
996                           0,
997                           0);
998
999     // Add the More Details link.
1000     layout->StartRow(0, column_set_id);
1001     more_details_ = link;
1002     more_details_->set_listener(this);
1003     more_details_->SetHorizontalAlignment(gfx::ALIGN_LEFT);
1004     layout->AddView(more_details_);
1005
1006     // Add the arrow after the More Details link.
1007     ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();
1008     arrow_toggle_ = new views::ImageButton(this);
1009     arrow_toggle_->SetImage(views::Button::STATE_NORMAL,
1010                             rb.GetImageSkiaNamed(IDR_DOWN_ARROW));
1011     layout->AddView(arrow_toggle_);
1012   }
1013 }
1014
1015 ExpandableContainerView::~ExpandableContainerView() {
1016 }
1017
1018 void ExpandableContainerView::ButtonPressed(
1019     views::Button* sender, const ui::Event& event) {
1020   ToggleDetailLevel();
1021 }
1022
1023 void ExpandableContainerView::LinkClicked(
1024     views::Link* source, int event_flags) {
1025   ToggleDetailLevel();
1026 }
1027
1028 void ExpandableContainerView::AnimationProgressed(
1029     const gfx::Animation* animation) {
1030   DCHECK_EQ(&slide_animation_, animation);
1031   if (details_view_)
1032     details_view_->AnimateToState(animation->GetCurrentValue());
1033 }
1034
1035 void ExpandableContainerView::AnimationEnded(const gfx::Animation* animation) {
1036   if (arrow_toggle_) {
1037     if (animation->GetCurrentValue() != 0.0) {
1038       arrow_toggle_->SetImage(
1039           views::Button::STATE_NORMAL,
1040           ui::ResourceBundle::GetSharedInstance().GetImageSkiaNamed(
1041               IDR_UP_ARROW));
1042     } else {
1043       arrow_toggle_->SetImage(
1044           views::Button::STATE_NORMAL,
1045           ui::ResourceBundle::GetSharedInstance().GetImageSkiaNamed(
1046               IDR_DOWN_ARROW));
1047     }
1048   }
1049   if (more_details_) {
1050     more_details_->SetText(expanded_ ?
1051         l10n_util::GetStringUTF16(IDS_EXTENSIONS_HIDE_DETAILS) :
1052         l10n_util::GetStringUTF16(IDS_EXTENSIONS_SHOW_DETAILS));
1053   }
1054 }
1055
1056 void ExpandableContainerView::ChildPreferredSizeChanged(views::View* child) {
1057   owner_->ContentsChanged();
1058 }
1059
1060 void ExpandableContainerView::ToggleDetailLevel() {
1061   expanded_ = !expanded_;
1062
1063   if (slide_animation_.IsShowing())
1064     slide_animation_.Hide();
1065   else
1066     slide_animation_.Show();
1067 }
1068
1069 void ExpandableContainerView::ExpandWithoutAnimation() {
1070   expanded_ = true;
1071   details_view_->AnimateToState(1.0);
1072 }
1073
1074 // static
1075 ExtensionInstallPrompt::ShowDialogCallback
1076 ExtensionInstallPrompt::GetDefaultShowDialogCallback() {
1077   return base::Bind(&ShowExtensionInstallDialogImpl);
1078 }