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