Home | History | Annotate | Download | only in extensions
      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 <vector>
      6 
      7 #include "base/basictypes.h"
      8 #include "base/command_line.h"
      9 #include "base/compiler_specific.h"
     10 #include "base/i18n/rtl.h"
     11 #include "base/strings/string_util.h"
     12 #include "base/strings/utf_string_conversions.h"
     13 #include "chrome/browser/extensions/bundle_installer.h"
     14 #include "chrome/browser/extensions/extension_install_prompt.h"
     15 #include "chrome/browser/profiles/profile.h"
     16 #include "chrome/browser/ui/views/constrained_window_views.h"
     17 #include "chrome/common/chrome_switches.h"
     18 #include "chrome/common/extensions/extension.h"
     19 #include "chrome/installer/util/browser_distribution.h"
     20 #include "content/public/browser/page_navigator.h"
     21 #include "content/public/browser/web_contents.h"
     22 #include "grit/chromium_strings.h"
     23 #include "grit/generated_resources.h"
     24 #include "grit/google_chrome_strings.h"
     25 #include "grit/theme_resources.h"
     26 #include "ui/base/animation/animation_delegate.h"
     27 #include "ui/base/animation/slide_animation.h"
     28 #include "ui/base/l10n/l10n_util.h"
     29 #include "ui/base/resource/resource_bundle.h"
     30 #include "ui/gfx/transform.h"
     31 #include "ui/views/border.h"
     32 #include "ui/views/controls/button/image_button.h"
     33 #include "ui/views/controls/image_view.h"
     34 #include "ui/views/controls/label.h"
     35 #include "ui/views/controls/link.h"
     36 #include "ui/views/controls/link_listener.h"
     37 #include "ui/views/controls/scroll_view.h"
     38 #include "ui/views/controls/separator.h"
     39 #include "ui/views/layout/box_layout.h"
     40 #include "ui/views/layout/grid_layout.h"
     41 #include "ui/views/layout/layout_constants.h"
     42 #include "ui/views/view.h"
     43 #include "ui/views/widget/widget.h"
     44 #include "ui/views/window/dialog_delegate.h"
     45 
     46 using content::OpenURLParams;
     47 using content::Referrer;
     48 using extensions::BundleInstaller;
     49 
     50 namespace {
     51 
     52 // Size of extension icon in top left of dialog.
     53 const int kIconSize = 69;
     54 
     55 // The dialog width.
     56 const int kDialogWidth = 385;
     57 
     58 // The dialog will resize based on its content, but this sets a maximum height
     59 // before overflowing a scrollbar.
     60 const int kDialogMaxHeight = 300;
     61 
     62 // Width of the left column of the dialog when the extension requests
     63 // permissions.
     64 const int kPermissionsLeftColumnWidth = 250;
     65 
     66 // Width of the left column of the dialog when the extension requests no
     67 // permissions.
     68 const int kNoPermissionsLeftColumnWidth = 200;
     69 
     70 // Width of the left column for bundle install prompts. There's only one column
     71 // in this case, so make it wider than normal.
     72 const int kBundleLeftColumnWidth = 300;
     73 
     74 // Width of the left column for external install prompts. The text is long in
     75 // this case, so make it wider than normal.
     76 const int kExternalInstallLeftColumnWidth = 350;
     77 
     78 // Maximum height of the retained files view.
     79 const int kMaxRetainedFilesHeight = 100;
     80 
     81 typedef std::vector<string16> PermissionDetails;
     82 
     83 void AddResourceIcon(const gfx::ImageSkia* skia_image, void* data) {
     84   views::View* parent = static_cast<views::View*>(data);
     85   views::ImageView* image_view = new views::ImageView();
     86   image_view->SetImage(*skia_image);
     87   parent->AddChildView(image_view);
     88 }
     89 
     90 // Creates a string for displaying |message| to the user. If it has to look
     91 // like a entry in a bullet point list, one is added.
     92 string16 PrepareForDisplay(const string16& message, bool bullet_point) {
     93   return bullet_point ? l10n_util::GetStringFUTF16(
     94       IDS_EXTENSION_PERMISSION_LINE,
     95       message) : message;
     96 }
     97 
     98 // Implements the extension installation dialog for TOOLKIT_VIEWS.
     99 class ExtensionInstallDialogView : public views::DialogDelegateView,
    100                                    public views::LinkListener {
    101  public:
    102   ExtensionInstallDialogView(content::PageNavigator* navigator,
    103                              ExtensionInstallPrompt::Delegate* delegate,
    104                              const ExtensionInstallPrompt::Prompt& prompt);
    105   virtual ~ExtensionInstallDialogView();
    106 
    107   // Called when one of the child elements has expanded/collapsed.
    108   void ContentsChanged();
    109 
    110  private:
    111   // views::DialogDelegateView:
    112   virtual int GetDialogButtons() const OVERRIDE;
    113   virtual string16 GetDialogButtonLabel(ui::DialogButton button) const OVERRIDE;
    114   virtual int GetDefaultDialogButton() const OVERRIDE;
    115   virtual bool Cancel() OVERRIDE;
    116   virtual bool Accept() OVERRIDE;
    117   virtual ui::ModalType GetModalType() const OVERRIDE;
    118   virtual string16 GetWindowTitle() const OVERRIDE;
    119   virtual void Layout() OVERRIDE;
    120   virtual gfx::Size GetPreferredSize() OVERRIDE;
    121 
    122   // views::LinkListener:
    123   virtual void LinkClicked(views::Link* source, int event_flags) OVERRIDE;
    124 
    125   bool is_inline_install() const {
    126     return prompt_.type() == ExtensionInstallPrompt::INLINE_INSTALL_PROMPT;
    127   }
    128 
    129   bool is_bundle_install() const {
    130     return prompt_.type() == ExtensionInstallPrompt::BUNDLE_INSTALL_PROMPT;
    131   }
    132 
    133   bool is_external_install() const {
    134     return prompt_.type() == ExtensionInstallPrompt::EXTERNAL_INSTALL_PROMPT;
    135   }
    136 
    137   content::PageNavigator* navigator_;
    138   ExtensionInstallPrompt::Delegate* delegate_;
    139   ExtensionInstallPrompt::Prompt prompt_;
    140 
    141   // The scroll view containing all the details for the dialog (including all
    142   // collapsible/expandable sections).
    143   views::ScrollView* scroll_view_;
    144 
    145   // The container view for the scroll view.
    146   views::View* scrollable_;
    147 
    148   // The preferred size of the dialog.
    149   gfx::Size dialog_size_;
    150 
    151   DISALLOW_COPY_AND_ASSIGN(ExtensionInstallDialogView);
    152 };
    153 
    154 // A view to display text with an expandable details section.
    155 class ExpandableContainerView : public views::View,
    156                                 public views::ButtonListener,
    157                                 public views::LinkListener,
    158                                 public ui::AnimationDelegate {
    159  public:
    160   ExpandableContainerView(ExtensionInstallDialogView* owner,
    161                           const string16& description,
    162                           const PermissionDetails& details,
    163                           int horizontal_space,
    164                           bool show_bullets);
    165   virtual ~ExpandableContainerView();
    166 
    167   // views::View:
    168   virtual void ChildPreferredSizeChanged(views::View* child) OVERRIDE;
    169 
    170   // views::ButtonListener:
    171   virtual void ButtonPressed(views::Button* sender,
    172                              const ui::Event& event) OVERRIDE;
    173 
    174   // views::LinkListener:
    175   virtual void LinkClicked(views::Link* source, int event_flags) OVERRIDE;
    176 
    177   // ui::AnimationDelegate:
    178   virtual void AnimationProgressed(const ui::Animation* animation) OVERRIDE;
    179   virtual void AnimationEnded(const ui::Animation* animation) OVERRIDE;
    180 
    181  private:
    182   // A view which displays all the details of an IssueAdviceInfoEntry.
    183   class DetailsView : public views::View {
    184    public:
    185     explicit DetailsView(int horizontal_space, bool show_bullets);
    186     virtual ~DetailsView() {}
    187 
    188     // views::View:
    189     virtual gfx::Size GetPreferredSize() OVERRIDE;
    190 
    191     void AddDetail(const string16& detail);
    192 
    193     // Animates this to be a height proportional to |state|.
    194     void AnimateToState(double state);
    195 
    196    private:
    197     views::GridLayout* layout_;
    198     double state_;
    199 
    200     // Whether to show bullets in front of each item in the details.
    201     bool show_bullets_;
    202 
    203     DISALLOW_COPY_AND_ASSIGN(DetailsView);
    204   };
    205 
    206   // Expand/Collapse the detail section for this ExpandableContainerView.
    207   void ToggleDetailLevel();
    208 
    209   // The dialog that owns |this|. It's also an ancestor in the View hierarchy.
    210   ExtensionInstallDialogView* owner_;
    211 
    212   // A view for showing |issue_advice.details|.
    213   DetailsView* details_view_;
    214 
    215   // The '>' zippy control.
    216   views::ImageView* arrow_view_;
    217 
    218   ui::SlideAnimation slide_animation_;
    219 
    220   // The 'more details' link shown under the heading (changes to 'hide details'
    221   // when the details section is expanded).
    222   views::Link* more_details_;
    223 
    224   // The up/down arrow next to the 'more detail' link (points up/down depending
    225   // on whether the details section is expanded).
    226   views::ImageButton* arrow_toggle_;
    227 
    228   // Whether the details section is expanded.
    229   bool expanded_;
    230 
    231   DISALLOW_COPY_AND_ASSIGN(ExpandableContainerView);
    232 };
    233 
    234 void ShowExtensionInstallDialogImpl(
    235     const ExtensionInstallPrompt::ShowParams& show_params,
    236     ExtensionInstallPrompt::Delegate* delegate,
    237     const ExtensionInstallPrompt::Prompt& prompt) {
    238   DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
    239   CreateBrowserModalDialogViews(
    240       new ExtensionInstallDialogView(show_params.navigator, delegate, prompt),
    241       show_params.parent_window)->Show();
    242 }
    243 
    244 // A ScrollView that imposes a maximum size on its viewport but sizes its
    245 // contents to its preferred size.
    246 class MaxSizeScrollView : public views::ScrollView {
    247  public:
    248   MaxSizeScrollView(int max_height, int max_width);
    249   // Overridden from views::View:
    250   virtual gfx::Size GetPreferredSize() OVERRIDE;
    251   virtual void Layout() OVERRIDE;
    252 
    253  private:
    254   const int max_height_;
    255   const int max_width_;
    256 
    257   DISALLOW_COPY_AND_ASSIGN(MaxSizeScrollView);
    258 };
    259 
    260 MaxSizeScrollView::MaxSizeScrollView(int max_height, int max_width)
    261     : max_height_(max_height),
    262       max_width_(max_width) {}
    263 
    264 gfx::Size MaxSizeScrollView::GetPreferredSize() {
    265   gfx::Size size = contents()->GetPreferredSize();
    266   size.SetToMin(gfx::Size(max_width_, max_height_));
    267   gfx::Insets insets = GetInsets();
    268   size.Enlarge(insets.width(), insets.height());
    269   size.Enlarge(GetScrollBarWidth(), GetScrollBarHeight());
    270   return size;
    271 }
    272 
    273 void MaxSizeScrollView::Layout() {
    274   contents()->SizeToPreferredSize();
    275   views::ScrollView::Layout();
    276 }
    277 
    278 }  // namespace
    279 
    280 ExtensionInstallDialogView::ExtensionInstallDialogView(
    281     content::PageNavigator* navigator,
    282     ExtensionInstallPrompt::Delegate* delegate,
    283     const ExtensionInstallPrompt::Prompt& prompt)
    284     : navigator_(navigator),
    285       delegate_(delegate),
    286       prompt_(prompt) {
    287   // Possible grid layouts:
    288   // Inline install
    289   //      w/ permissions                 no permissions
    290   // +--------------------+------+  +--------------+------+
    291   // | heading            | icon |  | heading      | icon |
    292   // +--------------------|      |  +--------------|      |
    293   // | rating             |      |  | rating       |      |
    294   // +--------------------|      |  +--------------+      |
    295   // | user_count         |      |  | user_count   |      |
    296   // +--------------------|      |  +--------------|      |
    297   // | store_link         |      |  | store_link   |      |
    298   // +--------------------+------+  +--------------+------+
    299   // |      separator            |
    300   // +--------------------+------+
    301   // | permissions_header |      |
    302   // +--------------------+------+
    303   // | permission1        |      |
    304   // +--------------------+------+
    305   // | permission2        |      |
    306   // +--------------------+------+
    307   //
    308   // Regular install
    309   // w/ permissions XOR oauth issues    no permissions
    310   // +--------------------+------+  +--------------+------+
    311   // | heading            | icon |  | heading      | icon |
    312   // +--------------------|      |  +--------------+------+
    313   // | permissions_header |      |
    314   // +--------------------|      |
    315   // | permission1        |      |
    316   // +--------------------|      |
    317   // | permission2        |      |
    318   // +--------------------+------+
    319   //
    320   // w/ permissions AND oauth issues
    321   // +--------------------+------+
    322   // | heading            | icon |
    323   // +--------------------|      |
    324   // | permissions_header |      |
    325   // +--------------------|      |
    326   // | permission1        |      |
    327   // +--------------------|      |
    328   // | permission2        |      |
    329   // +--------------------+------+
    330   // | oauth header              |
    331   // +---------------------------+
    332   // | oauth issue 1             |
    333   // +---------------------------+
    334   // | oauth issue 2             |
    335   // +---------------------------+
    336 
    337   scroll_view_ = new views::ScrollView();
    338   AddChildView(scroll_view_);
    339   scrollable_ = new views::View();
    340   scroll_view_->SetContents(scrollable_);
    341 
    342   views::GridLayout* layout = views::GridLayout::CreatePanel(scrollable_);
    343   scrollable_->SetLayoutManager(layout);
    344 
    345   int column_set_id = 0;
    346   views::ColumnSet* column_set = layout->AddColumnSet(column_set_id);
    347   int left_column_width =
    348       (prompt.ShouldShowPermissions() + prompt.GetOAuthIssueCount() +
    349        prompt.GetRetainedFileCount()) > 0 ?
    350           kPermissionsLeftColumnWidth : kNoPermissionsLeftColumnWidth;
    351   if (is_bundle_install())
    352     left_column_width = kBundleLeftColumnWidth;
    353   if (is_external_install())
    354     left_column_width = kExternalInstallLeftColumnWidth;
    355 
    356   column_set->AddColumn(views::GridLayout::LEADING,
    357                         views::GridLayout::FILL,
    358                         0,  // no resizing
    359                         views::GridLayout::USE_PREF,
    360                         0,  // no fixed width
    361                         left_column_width);
    362   if (!is_bundle_install()) {
    363     column_set->AddPaddingColumn(0, views::kPanelHorizMargin);
    364     column_set->AddColumn(views::GridLayout::LEADING,
    365                           views::GridLayout::LEADING,
    366                           0,  // no resizing
    367                           views::GridLayout::USE_PREF,
    368                           0,  // no fixed width
    369                           kIconSize);
    370   }
    371 
    372   layout->StartRow(0, column_set_id);
    373 
    374   ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();
    375 
    376   views::Label* heading = new views::Label(prompt.GetHeading());
    377   heading->SetFont(rb.GetFont(ui::ResourceBundle::MediumFont));
    378   heading->SetMultiLine(true);
    379   heading->SetHorizontalAlignment(gfx::ALIGN_LEFT);
    380   heading->SizeToFit(left_column_width);
    381   layout->AddView(heading);
    382 
    383   if (!is_bundle_install()) {
    384     // Scale down to icon size, but allow smaller icons (don't scale up).
    385     const gfx::ImageSkia* image = prompt.icon().ToImageSkia();
    386     gfx::Size size(image->width(), image->height());
    387     if (size.width() > kIconSize || size.height() > kIconSize)
    388       size = gfx::Size(kIconSize, kIconSize);
    389     views::ImageView* icon = new views::ImageView();
    390     icon->SetImageSize(size);
    391     icon->SetImage(*image);
    392     icon->SetHorizontalAlignment(views::ImageView::CENTER);
    393     icon->SetVerticalAlignment(views::ImageView::CENTER);
    394     int icon_row_span = 1;
    395     if (is_inline_install()) {
    396       // Also span the rating, user_count and store_link rows.
    397       icon_row_span = 4;
    398     } else if (prompt.ShouldShowPermissions()) {
    399       size_t permission_count = prompt.GetPermissionCount();
    400       if (permission_count > 0) {
    401         // Also span the permission header and each of the permission rows (all
    402         // have a padding row above it).
    403         icon_row_span = 3 + permission_count * 2;
    404       } else {
    405         // This is the 'no special permissions' case, so span the line we add
    406         // (without a header) saying the extension has no special privileges.
    407         icon_row_span = 4;
    408       }
    409     } else if (prompt.GetOAuthIssueCount()) {
    410       // Also span the permission header and each of the permission rows (all
    411       // have a padding row above it).
    412       icon_row_span = 3 + prompt.GetOAuthIssueCount() * 2;
    413     } else if (prompt.GetRetainedFileCount()) {
    414       // Also span the permission header and the retained files container.
    415       icon_row_span = 4;
    416     }
    417     layout->AddView(icon, 1, icon_row_span);
    418   }
    419 
    420   if (is_inline_install()) {
    421     layout->StartRow(0, column_set_id);
    422     views::View* rating = new views::View();
    423     rating->SetLayoutManager(new views::BoxLayout(
    424         views::BoxLayout::kHorizontal, 0, 0, 0));
    425     layout->AddView(rating);
    426     prompt.AppendRatingStars(AddResourceIcon, rating);
    427 
    428     views::Label* rating_count = new views::Label(prompt.GetRatingCount());
    429     rating_count->SetFont(rb.GetFont(ui::ResourceBundle::SmallFont));
    430     // Add some space between the stars and the rating count.
    431     rating_count->set_border(views::Border::CreateEmptyBorder(0, 2, 0, 0));
    432     rating->AddChildView(rating_count);
    433 
    434     layout->StartRow(0, column_set_id);
    435     views::Label* user_count = new views::Label(prompt.GetUserCount());
    436     user_count->SetAutoColorReadabilityEnabled(false);
    437     user_count->SetEnabledColor(SK_ColorGRAY);
    438     user_count->SetFont(rb.GetFont(ui::ResourceBundle::SmallFont));
    439     layout->AddView(user_count);
    440 
    441     layout->StartRow(0, column_set_id);
    442     views::Link* store_link = new views::Link(
    443         l10n_util::GetStringUTF16(IDS_EXTENSION_PROMPT_STORE_LINK));
    444     store_link->SetFont(rb.GetFont(ui::ResourceBundle::SmallFont));
    445     store_link->set_listener(this);
    446     layout->AddView(store_link);
    447   }
    448 
    449   if (is_bundle_install()) {
    450     BundleInstaller::ItemList items = prompt.bundle()->GetItemsWithState(
    451         BundleInstaller::Item::STATE_PENDING);
    452     for (size_t i = 0; i < items.size(); ++i) {
    453       string16 extension_name = UTF8ToUTF16(items[i].localized_name);
    454       base::i18n::AdjustStringForLocaleDirection(&extension_name);
    455       layout->AddPaddingRow(0, views::kRelatedControlVerticalSpacing);
    456       layout->StartRow(0, column_set_id);
    457       views::Label* extension_label = new views::Label(
    458           PrepareForDisplay(extension_name, true));
    459       extension_label->SetMultiLine(true);
    460       extension_label->SetHorizontalAlignment(gfx::ALIGN_LEFT);
    461       extension_label->SizeToFit(left_column_width);
    462       layout->AddView(extension_label);
    463     }
    464   }
    465 
    466   if (prompt.ShouldShowPermissions()) {
    467     layout->AddPaddingRow(0, views::kRelatedControlVerticalSpacing);
    468 
    469     if (prompt.GetPermissionCount() > 0) {
    470       if (is_inline_install()) {
    471         layout->StartRow(0, column_set_id);
    472         layout->AddView(new views::Separator(views::Separator::HORIZONTAL),
    473                         3, 1, views::GridLayout::FILL, views::GridLayout::FILL);
    474         layout->AddPaddingRow(0, views::kRelatedControlVerticalSpacing);
    475       }
    476 
    477       layout->StartRow(0, column_set_id);
    478       views::Label* permissions_header = NULL;
    479       if (is_bundle_install()) {
    480         // We need to pass the Font in the constructor, rather than calling
    481         // SetFont later, because otherwise SizeToFit mis-judges the width
    482         // of the line.
    483         permissions_header = new views::Label(
    484             prompt.GetPermissionsHeading(),
    485             rb.GetFont(ui::ResourceBundle::MediumFont));
    486       } else {
    487         permissions_header = new views::Label(prompt.GetPermissionsHeading());
    488       }
    489       permissions_header->SetMultiLine(true);
    490       permissions_header->SetHorizontalAlignment(gfx::ALIGN_LEFT);
    491       permissions_header->SizeToFit(left_column_width);
    492       layout->AddView(permissions_header);
    493 
    494       for (size_t i = 0; i < prompt.GetPermissionCount(); ++i) {
    495         layout->AddPaddingRow(0, views::kRelatedControlVerticalSpacing);
    496         layout->StartRow(0, column_set_id);
    497         views::Label* permission_label = new views::Label(PrepareForDisplay(
    498             prompt.GetPermission(i), true));
    499         permission_label->SetMultiLine(true);
    500         permission_label->SetHorizontalAlignment(gfx::ALIGN_LEFT);
    501         permission_label->SizeToFit(left_column_width);
    502         layout->AddView(permission_label);
    503 
    504         // If we have more details to provide, show them in collapsed form.
    505         if (!prompt.GetPermissionsDetails(i).empty()) {
    506           layout->StartRow(0, column_set_id);
    507           PermissionDetails details;
    508           details.push_back(
    509               PrepareForDisplay(prompt.GetPermissionsDetails(i), false));
    510           ExpandableContainerView* details_container =
    511               new ExpandableContainerView(
    512                   this, string16(), details, left_column_width, false);
    513           layout->AddView(details_container);
    514         }
    515       }
    516     } else {
    517       layout->AddPaddingRow(0, views::kRelatedControlVerticalSpacing);
    518       layout->StartRow(0, column_set_id);
    519       views::Label* permission_label = new views::Label(
    520           l10n_util::GetStringUTF16(IDS_EXTENSION_NO_SPECIAL_PERMISSIONS));
    521       permission_label->SetMultiLine(true);
    522       permission_label->SetHorizontalAlignment(gfx::ALIGN_LEFT);
    523       permission_label->SizeToFit(left_column_width);
    524       layout->AddView(permission_label);
    525     }
    526   }
    527 
    528   if (prompt.GetOAuthIssueCount()) {
    529     // Slide in under the permissions, if there are any. If there are
    530     // permissions, the OAuth prompt stretches all the way to the right of the
    531     // dialog. If there are no permissions, the OAuth prompt just takes up the
    532     // left column.
    533     int space_for_oauth = left_column_width;
    534     if (prompt.GetPermissionCount()) {
    535       space_for_oauth += kIconSize;
    536       column_set = layout->AddColumnSet(++column_set_id);
    537       column_set->AddColumn(views::GridLayout::FILL,
    538                             views::GridLayout::FILL,
    539                             1,
    540                             views::GridLayout::USE_PREF,
    541                             0,  // no fixed width
    542                             space_for_oauth);
    543     }
    544 
    545     layout->StartRowWithPadding(0, column_set_id,
    546                                 0, views::kRelatedControlVerticalSpacing);
    547     views::Label* oauth_header = new views::Label(prompt.GetOAuthHeading());
    548     oauth_header->SetMultiLine(true);
    549     oauth_header->SetHorizontalAlignment(gfx::ALIGN_LEFT);
    550     oauth_header->SizeToFit(left_column_width);
    551     layout->AddView(oauth_header);
    552 
    553     for (size_t i = 0; i < prompt.GetOAuthIssueCount(); ++i) {
    554       layout->StartRowWithPadding(
    555           0, column_set_id,
    556           0, views::kRelatedControlVerticalSpacing);
    557 
    558       PermissionDetails details;
    559       const IssueAdviceInfoEntry& entry = prompt.GetOAuthIssue(i);
    560       for (size_t x = 0; x < entry.details.size(); ++x)
    561         details.push_back(entry.details[x]);
    562       ExpandableContainerView* issue_advice_view =
    563           new ExpandableContainerView(
    564               this, entry.description, details, space_for_oauth, false);
    565       layout->AddView(issue_advice_view);
    566     }
    567   }
    568   if (prompt.GetRetainedFileCount()) {
    569     // Slide in under the permissions or OAuth, if there are any. If there are
    570     // either, the retained files prompt stretches all the way to the right of
    571     // the dialog. If there are no permissions or OAuth, the retained files
    572     // prompt just takes up the left column.
    573     int space_for_files = left_column_width;
    574     if (prompt.GetPermissionCount() || prompt.GetOAuthIssueCount()) {
    575       space_for_files += kIconSize;
    576       column_set = layout->AddColumnSet(++column_set_id);
    577       column_set->AddColumn(views::GridLayout::FILL,
    578                             views::GridLayout::FILL,
    579                             1,
    580                             views::GridLayout::USE_PREF,
    581                             0,  // no fixed width
    582                             space_for_files);
    583     }
    584 
    585     layout->AddPaddingRow(0, views::kRelatedControlVerticalSpacing);
    586 
    587     layout->StartRow(0, column_set_id);
    588     views::Label* retained_files_header = NULL;
    589     retained_files_header =
    590         new views::Label(prompt.GetRetainedFilesHeadingWithCount());
    591     retained_files_header->SetMultiLine(true);
    592     retained_files_header->SetHorizontalAlignment(gfx::ALIGN_LEFT);
    593     retained_files_header->SizeToFit(space_for_files);
    594     layout->AddView(retained_files_header);
    595 
    596     layout->StartRow(0, column_set_id);
    597     PermissionDetails details;
    598     for (size_t i = 0; i < prompt.GetRetainedFileCount(); ++i)
    599       details.push_back(prompt.GetRetainedFile(i));
    600     ExpandableContainerView* issue_advice_view =
    601         new ExpandableContainerView(
    602             this, string16(), details, space_for_files, false);
    603     layout->AddView(issue_advice_view);
    604   }
    605 
    606   gfx::Size scrollable_size = scrollable_->GetPreferredSize();
    607   scrollable_->SetBoundsRect(gfx::Rect(scrollable_size));
    608   dialog_size_ = gfx::Size(
    609       kDialogWidth,
    610       std::min(scrollable_size.height(), kDialogMaxHeight));
    611 }
    612 
    613 ExtensionInstallDialogView::~ExtensionInstallDialogView() {}
    614 
    615 void ExtensionInstallDialogView::ContentsChanged() {
    616   Layout();
    617 }
    618 
    619 int ExtensionInstallDialogView::GetDialogButtons() const {
    620   int buttons = prompt_.GetDialogButtons();
    621   // Simply having just an OK button is *not* supported. See comment on function
    622   // GetDialogButtons in dialog_delegate.h for reasons.
    623   DCHECK_GT(buttons & ui::DIALOG_BUTTON_CANCEL, 0);
    624   return buttons;
    625 }
    626 
    627 string16 ExtensionInstallDialogView::GetDialogButtonLabel(
    628     ui::DialogButton button) const {
    629   switch (button) {
    630     case ui::DIALOG_BUTTON_OK:
    631       return prompt_.GetAcceptButtonLabel();
    632     case ui::DIALOG_BUTTON_CANCEL:
    633       return prompt_.HasAbortButtonLabel() ?
    634           prompt_.GetAbortButtonLabel() :
    635           l10n_util::GetStringUTF16(IDS_CANCEL);
    636     default:
    637       NOTREACHED();
    638       return string16();
    639   }
    640 }
    641 
    642 int ExtensionInstallDialogView::GetDefaultDialogButton() const {
    643   return ui::DIALOG_BUTTON_CANCEL;
    644 }
    645 
    646 bool ExtensionInstallDialogView::Cancel() {
    647   delegate_->InstallUIAbort(true);
    648   return true;
    649 }
    650 
    651 bool ExtensionInstallDialogView::Accept() {
    652   delegate_->InstallUIProceed();
    653   return true;
    654 }
    655 
    656 ui::ModalType ExtensionInstallDialogView::GetModalType() const {
    657   return ui::MODAL_TYPE_WINDOW;
    658 }
    659 
    660 string16 ExtensionInstallDialogView::GetWindowTitle() const {
    661   return prompt_.GetDialogTitle();
    662 }
    663 
    664 void ExtensionInstallDialogView::LinkClicked(views::Link* source,
    665                                              int event_flags) {
    666   GURL store_url(extension_urls::GetWebstoreItemDetailURLPrefix() +
    667                  prompt_.extension()->id());
    668   OpenURLParams params(
    669       store_url, Referrer(), NEW_FOREGROUND_TAB, content::PAGE_TRANSITION_LINK,
    670       false);
    671   navigator_->OpenURL(params);
    672   GetWidget()->Close();
    673 }
    674 
    675 void ExtensionInstallDialogView::Layout() {
    676   views::View* contents_view = scroll_view_->contents();
    677   int content_width = width();
    678   int content_height = contents_view->GetHeightForWidth(content_width);
    679   if (content_height > height()) {
    680     content_width -= scroll_view_->GetScrollBarWidth();
    681     content_height = contents_view->GetHeightForWidth(content_width);
    682   }
    683   contents_view->SetBounds(0, 0, content_width, content_height);
    684   scroll_view_->SetBounds(0, 0, width(), height());
    685 
    686   DialogDelegateView::Layout();
    687 }
    688 
    689 gfx::Size ExtensionInstallDialogView::GetPreferredSize() {
    690   return dialog_size_;
    691 }
    692 
    693 // static
    694 ExtensionInstallPrompt::ShowDialogCallback
    695 ExtensionInstallPrompt::GetDefaultShowDialogCallback() {
    696   return base::Bind(&ShowExtensionInstallDialogImpl);
    697 }
    698 
    699 // ExpandableContainerView::DetailsView ----------------------------------------
    700 
    701 ExpandableContainerView::DetailsView::DetailsView(int horizontal_space,
    702                                                   bool show_bullets)
    703     : layout_(new views::GridLayout(this)),
    704       state_(0),
    705       show_bullets_(show_bullets) {
    706   SetLayoutManager(layout_);
    707   views::ColumnSet* column_set = layout_->AddColumnSet(0);
    708   column_set->AddPaddingColumn(0, views::kRelatedControlHorizontalSpacing);
    709   column_set->AddColumn(views::GridLayout::LEADING,
    710                         views::GridLayout::LEADING,
    711                         0,
    712                         views::GridLayout::FIXED,
    713                         horizontal_space,
    714                         0);
    715 }
    716 
    717 void ExpandableContainerView::DetailsView::AddDetail(const string16& detail) {
    718   layout_->StartRowWithPadding(0, 0,
    719                                0, views::kRelatedControlSmallVerticalSpacing);
    720   views::Label* detail_label =
    721       new views::Label(PrepareForDisplay(detail, show_bullets_));
    722   detail_label->SetMultiLine(true);
    723   detail_label->SetHorizontalAlignment(gfx::ALIGN_LEFT);
    724   layout_->AddView(detail_label);
    725 }
    726 
    727 gfx::Size ExpandableContainerView::DetailsView::GetPreferredSize() {
    728   gfx::Size size = views::View::GetPreferredSize();
    729   return gfx::Size(size.width(), size.height() * state_);
    730 }
    731 
    732 void ExpandableContainerView::DetailsView::AnimateToState(double state) {
    733   state_ = state;
    734   PreferredSizeChanged();
    735   SchedulePaint();
    736 }
    737 
    738 // ExpandableContainerView -----------------------------------------------------
    739 
    740 ExpandableContainerView::ExpandableContainerView(
    741     ExtensionInstallDialogView* owner,
    742     const string16& description,
    743     const PermissionDetails& details,
    744     int horizontal_space,
    745     bool show_bullets)
    746     : owner_(owner),
    747       details_view_(NULL),
    748       arrow_view_(NULL),
    749       slide_animation_(this),
    750       expanded_(false) {
    751   views::GridLayout* layout = new views::GridLayout(this);
    752   SetLayoutManager(layout);
    753   int column_set_id = 0;
    754   views::ColumnSet* column_set = layout->AddColumnSet(column_set_id);
    755   column_set->AddColumn(views::GridLayout::LEADING,
    756                         views::GridLayout::LEADING,
    757                         0,
    758                         views::GridLayout::USE_PREF,
    759                         0,
    760                         0);
    761   if (!description.empty()) {
    762     layout->StartRow(0, column_set_id);
    763 
    764     views::Label* description_label =
    765         new views::Label(PrepareForDisplay(description, true));
    766     description_label->SetMultiLine(true);
    767     description_label->SetHorizontalAlignment(gfx::ALIGN_LEFT);
    768     description_label->SizeToFit(horizontal_space);
    769     layout->AddView(description_label);
    770   }
    771 
    772   if (details.empty())
    773     return;
    774 
    775   details_view_ = new DetailsView(horizontal_space, show_bullets);
    776 
    777   layout->StartRow(0, column_set_id);
    778   layout->AddView(details_view_);
    779 
    780   for (size_t i = 0; i < details.size(); ++i)
    781     details_view_->AddDetail(details[i]);
    782 
    783   // Prepare the columns for the More Details row.
    784   column_set = layout->AddColumnSet(++column_set_id);
    785   column_set->AddPaddingColumn(0, views::kRelatedControlHorizontalSpacing);
    786   column_set->AddColumn(views::GridLayout::LEADING,
    787                         views::GridLayout::LEADING,
    788                         0,
    789                         views::GridLayout::USE_PREF,
    790                         0,
    791                         0);
    792   column_set->AddPaddingColumn(0, views::kRelatedControlHorizontalSpacing);
    793   column_set->AddColumn(views::GridLayout::LEADING,
    794                        views::GridLayout::LEADING,
    795                        0,
    796                        views::GridLayout::USE_PREF,
    797                        0,
    798                        0);
    799   column_set->AddColumn(views::GridLayout::LEADING,
    800                         views::GridLayout::LEADING,
    801                         0,
    802                         views::GridLayout::USE_PREF,
    803                         0,
    804                         0);
    805 
    806   // Add the More Details link.
    807   layout->StartRow(0, column_set_id);
    808   more_details_ = new views::Link(
    809       l10n_util::GetStringUTF16(IDS_EXTENSIONS_SHOW_DETAILS));
    810   more_details_->set_listener(this);
    811   more_details_->SetHorizontalAlignment(gfx::ALIGN_LEFT);
    812   layout->AddView(more_details_);
    813 
    814   // Add the arrow after the More Details link.
    815   ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();
    816   arrow_toggle_ = new views::ImageButton(this);
    817   arrow_toggle_->SetImage(views::Button::STATE_NORMAL,
    818                           rb.GetImageSkiaNamed(IDR_DOWN_ARROW));
    819   layout->AddView(arrow_toggle_);
    820 }
    821 
    822 ExpandableContainerView::~ExpandableContainerView() {
    823 }
    824 
    825 void ExpandableContainerView::ButtonPressed(
    826     views::Button* sender, const ui::Event& event) {
    827   ToggleDetailLevel();
    828 }
    829 
    830 void ExpandableContainerView::LinkClicked(
    831     views::Link* source, int event_flags) {
    832   ToggleDetailLevel();
    833 }
    834 
    835 void ExpandableContainerView::AnimationProgressed(
    836     const ui::Animation* animation) {
    837   DCHECK_EQ(&slide_animation_, animation);
    838   if (details_view_)
    839     details_view_->AnimateToState(animation->GetCurrentValue());
    840 }
    841 
    842 void ExpandableContainerView::AnimationEnded(const ui::Animation* animation) {
    843   if (animation->GetCurrentValue() != 0.0) {
    844     arrow_toggle_->SetImage(
    845         views::Button::STATE_NORMAL,
    846         ui::ResourceBundle::GetSharedInstance().GetImageSkiaNamed(
    847             IDR_UP_ARROW));
    848   } else {
    849     arrow_toggle_->SetImage(
    850         views::Button::STATE_NORMAL,
    851         ui::ResourceBundle::GetSharedInstance().GetImageSkiaNamed(
    852             IDR_DOWN_ARROW));
    853   }
    854 
    855   more_details_->SetText(expanded_ ?
    856       l10n_util::GetStringUTF16(IDS_EXTENSIONS_HIDE_DETAILS) :
    857       l10n_util::GetStringUTF16(IDS_EXTENSIONS_SHOW_DETAILS));
    858 }
    859 
    860 void ExpandableContainerView::ChildPreferredSizeChanged(views::View* child) {
    861   owner_->ContentsChanged();
    862 }
    863 
    864 void ExpandableContainerView::ToggleDetailLevel() {
    865   expanded_ = !expanded_;
    866 
    867   if (slide_animation_.IsShowing())
    868     slide_animation_.Hide();
    869   else
    870     slide_animation_.Show();
    871 }
    872