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