| // Copyright (c) 2012 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "chrome/browser/ui/views/page_info/page_info_bubble_view.h" |
| |
| #include <stddef.h> |
| |
| #include <algorithm> |
| #include <utility> |
| |
| #include "base/i18n/rtl.h" |
| #include "base/macros.h" |
| #include "base/strings/string16.h" |
| #include "base/strings/string_util.h" |
| #include "base/strings/utf_string_conversions.h" |
| #include "base/task/post_task.h" |
| #include "build/build_config.h" |
| #include "chrome/browser/certificate_viewer.h" |
| #include "chrome/browser/infobars/infobar_service.h" |
| #include "chrome/browser/platform_util.h" |
| #include "chrome/browser/profiles/profile.h" |
| #include "chrome/browser/ui/browser.h" |
| #include "chrome/browser/ui/browser_window.h" |
| #include "chrome/browser/ui/page_info/page_info.h" |
| #include "chrome/browser/ui/page_info/page_info_dialog.h" |
| #include "chrome/browser/ui/view_ids.h" |
| #include "chrome/browser/ui/views/accessibility/non_accessible_image_view.h" |
| #include "chrome/browser/ui/views/bubble_anchor_util_views.h" |
| #include "chrome/browser/ui/views/chrome_layout_provider.h" |
| #include "chrome/browser/ui/views/chrome_typography.h" |
| #include "chrome/browser/ui/views/collected_cookies_views.h" |
| #include "chrome/browser/ui/views/frame/browser_view.h" |
| #include "chrome/browser/ui/views/hover_button.h" |
| #include "chrome/browser/ui/views/location_bar/location_bar_view.h" |
| #include "chrome/browser/ui/views/location_bar/location_icon_view.h" |
| #include "chrome/browser/ui/views/page_info/chosen_object_view.h" |
| #include "chrome/browser/ui/views/page_info/permission_selector_row.h" |
| #include "chrome/common/url_constants.h" |
| #include "components/content_settings/core/common/content_settings_types.h" |
| #include "components/security_state/core/security_state.h" |
| #include "components/strings/grit/components_chromium_strings.h" |
| #include "components/strings/grit/components_strings.h" |
| #include "content/public/browser/browser_task_traits.h" |
| #include "content/public/browser/browser_thread.h" |
| #include "extensions/common/constants.h" |
| #include "ui/base/l10n/l10n_util.h" |
| #include "ui/base/models/simple_menu_model.h" |
| #include "ui/base/resource/resource_bundle.h" |
| #include "ui/gfx/canvas.h" |
| #include "ui/gfx/font_list.h" |
| #include "ui/gfx/geometry/insets.h" |
| #include "ui/gfx/image/image.h" |
| #include "ui/views/border.h" |
| #include "ui/views/bubble/bubble_frame_view.h" |
| #include "ui/views/controls/button/image_button.h" |
| #include "ui/views/controls/button/label_button.h" |
| #include "ui/views/controls/button/md_text_button.h" |
| #include "ui/views/controls/combobox/combobox.h" |
| #include "ui/views/controls/image_view.h" |
| #include "ui/views/controls/label.h" |
| #include "ui/views/controls/link.h" |
| #include "ui/views/controls/styled_label.h" |
| #include "ui/views/layout/box_layout.h" |
| #include "ui/views/layout/grid_layout.h" |
| #include "ui/views/layout/layout_manager.h" |
| #include "ui/views/view.h" |
| #include "ui/views/widget/widget.h" |
| #include "ui/views/widget/widget_observer.h" |
| #include "ui/views/window/dialog_client_view.h" |
| #include "url/gurl.h" |
| |
| #if defined(SAFE_BROWSING_DB_LOCAL) |
| #include "chrome/browser/safe_browsing/chrome_password_protection_service.h" |
| #endif |
| |
| using bubble_anchor_util::AnchorConfiguration; |
| using bubble_anchor_util::GetPageInfoAnchorRect; |
| using bubble_anchor_util::GetPageInfoAnchorConfiguration; |
| |
| namespace { |
| |
| // General constants ----------------------------------------------------------- |
| |
| // Bubble width constraints. |
| constexpr int kMinBubbleWidth = 320; |
| constexpr int kMaxBubbleWidth = 1000; |
| |
| SkColor GetRelatedTextColor() { |
| views::Label label; |
| return views::style::GetColor(label, views::style::CONTEXT_LABEL, |
| views::style::STYLE_PRIMARY); |
| } |
| |
| // Adds a ColumnSet on |layout| with a single View column and padding columns |
| // on either side of it with |margin| width. |
| void AddColumnWithSideMargin(views::GridLayout* layout, int margin, int id) { |
| views::ColumnSet* column_set = layout->AddColumnSet(id); |
| column_set->AddPaddingColumn(views::GridLayout::kFixedSize, margin); |
| column_set->AddColumn(views::GridLayout::FILL, views::GridLayout::FILL, 1.0, |
| views::GridLayout::USE_PREF, 0, 0); |
| column_set->AddPaddingColumn(views::GridLayout::kFixedSize, margin); |
| } |
| |
| // Formats strings and returns the |gfx::Range| of the newly inserted string. |
| gfx::Range GetRangeForFormatString(int string_id, |
| const base::string16& insert_string, |
| base::string16* final_string) { |
| size_t offset; |
| *final_string = l10n_util::GetStringFUTF16(string_id, insert_string, &offset); |
| return gfx::Range(offset, offset + insert_string.length()); |
| } |
| |
| // Creates a button that formats the string given by |title_resource_id| with |
| // |secondary_text| and displays the latter part in the secondary text color. |
| std::unique_ptr<HoverButton> CreateMoreInfoButton( |
| views::ButtonListener* listener, |
| const gfx::ImageSkia& image_icon, |
| int title_resource_id, |
| const base::string16& secondary_text, |
| int click_target_id, |
| const base::string16& tooltip_text) { |
| auto icon = std::make_unique<NonAccessibleImageView>(); |
| icon->SetImage(image_icon); |
| auto button = std::make_unique<HoverButton>( |
| listener, std::move(icon), base::string16(), base::string16()); |
| |
| if (secondary_text.empty()) { |
| button->SetTitleTextWithHintRange( |
| l10n_util::GetStringUTF16(title_resource_id), |
| gfx::Range::InvalidRange()); |
| } else { |
| base::string16 title_text; |
| gfx::Range secondary_text_range = |
| GetRangeForFormatString(title_resource_id, secondary_text, &title_text); |
| button->SetTitleTextWithHintRange(title_text, secondary_text_range); |
| } |
| |
| button->set_id(click_target_id); |
| button->SetTooltipText(tooltip_text); |
| return button; |
| } |
| |
| std::unique_ptr<views::View> CreateSiteSettingsLink( |
| const int side_margin, |
| PageInfoBubbleView* listener) { |
| const base::string16& tooltip = |
| l10n_util::GetStringUTF16(IDS_PAGE_INFO_SITE_SETTINGS_TOOLTIP); |
| return CreateMoreInfoButton( |
| listener, PageInfoUI::GetSiteSettingsIcon(GetRelatedTextColor()), |
| IDS_PAGE_INFO_SITE_SETTINGS_LINK, base::string16(), |
| PageInfoBubbleView::VIEW_ID_PAGE_INFO_LINK_OR_BUTTON_SITE_SETTINGS, |
| tooltip); |
| } |
| |
| } // namespace |
| |
| // |BubbleHeaderView| is the UI element (view) that represents the header of the |
| // |PageInfoBubbleView|. The header shows the status of the site's |
| // identity check and the name of the site's identity. |
| class BubbleHeaderView : public views::View { |
| public: |
| BubbleHeaderView(views::ButtonListener* button_listener, |
| views::StyledLabelListener* styled_label_listener, |
| int side_margin); |
| ~BubbleHeaderView() override; |
| |
| // Sets the security summary for the current page. |
| void SetSummary(const base::string16& summary_text); |
| |
| // Sets the security details for the current page. |
| void SetDetails(const base::string16& details_text); |
| |
| void AddResetDecisionsLabel(); |
| |
| void AddPasswordReuseButtons(); |
| |
| private: |
| // The listener for the buttons in this view. |
| views::ButtonListener* button_listener_; |
| |
| // The listener for the styled labels in this view. |
| views::StyledLabelListener* styled_label_listener_; |
| |
| // The label that displays the status of the identity check for this site. |
| // Includes a link to open the Chrome Help Center article about connection |
| // security. |
| views::StyledLabel* security_details_label_; |
| |
| // A container for the styled label with a link for resetting cert decisions. |
| // This is only shown sometimes, so we use a container to keep track of |
| // where to place it (if needed). |
| views::View* reset_decisions_label_container_; |
| views::StyledLabel* reset_cert_decisions_label_; |
| |
| // A container for the label buttons used to change password or mark the site |
| // as safe. |
| views::View* password_reuse_button_container_; |
| views::LabelButton* change_password_button_; |
| views::LabelButton* whitelist_password_reuse_button_; |
| |
| DISALLOW_COPY_AND_ASSIGN(BubbleHeaderView); |
| }; |
| |
| // The regular PageInfoBubbleView is not supported for internal Chrome pages and |
| // extension pages. Instead of the |PageInfoBubbleView|, the |
| // |InternalPageInfoBubbleView| is displayed. |
| class InternalPageInfoBubbleView : public PageInfoBubbleViewBase { |
| public: |
| // If |anchor_view| is nullptr, or has no Widget, |parent_window| may be |
| // provided to ensure this bubble is closed when the parent closes. |
| InternalPageInfoBubbleView(views::View* anchor_view, |
| const gfx::Rect& anchor_rect, |
| gfx::NativeView parent_window, |
| content::WebContents* web_contents, |
| const GURL& url); |
| ~InternalPageInfoBubbleView() override; |
| |
| DISALLOW_COPY_AND_ASSIGN(InternalPageInfoBubbleView); |
| }; |
| |
| //////////////////////////////////////////////////////////////////////////////// |
| // Bubble Header |
| //////////////////////////////////////////////////////////////////////////////// |
| |
| BubbleHeaderView::BubbleHeaderView( |
| views::ButtonListener* button_listener, |
| views::StyledLabelListener* styled_label_listener, |
| int side_margin) |
| : button_listener_(button_listener), |
| styled_label_listener_(styled_label_listener), |
| security_details_label_(nullptr), |
| reset_decisions_label_container_(nullptr), |
| reset_cert_decisions_label_(nullptr), |
| password_reuse_button_container_(nullptr), |
| change_password_button_(nullptr), |
| whitelist_password_reuse_button_(nullptr) { |
| views::GridLayout* layout = |
| SetLayoutManager(std::make_unique<views::GridLayout>(this)); |
| |
| const int label_column_status = 1; |
| AddColumnWithSideMargin(layout, side_margin, label_column_status); |
| |
| layout->StartRow(views::GridLayout::kFixedSize, label_column_status); |
| |
| security_details_label_ = |
| new views::StyledLabel(base::string16(), styled_label_listener); |
| security_details_label_->set_id( |
| PageInfoBubbleView::VIEW_ID_PAGE_INFO_LABEL_SECURITY_DETAILS); |
| layout->AddView(security_details_label_, 1.0, 1.0, views::GridLayout::FILL, |
| views::GridLayout::LEADING); |
| |
| layout->StartRow(views::GridLayout::kFixedSize, label_column_status); |
| reset_decisions_label_container_ = new views::View(); |
| reset_decisions_label_container_->SetLayoutManager( |
| std::make_unique<views::BoxLayout>(views::BoxLayout::kHorizontal)); |
| layout->AddView(reset_decisions_label_container_, 1.0, 1.0, |
| views::GridLayout::FILL, views::GridLayout::LEADING); |
| |
| layout->StartRow(views::GridLayout::kFixedSize, label_column_status); |
| password_reuse_button_container_ = new views::View(); |
| layout->AddView(password_reuse_button_container_, 1, 1, |
| views::GridLayout::FILL, views::GridLayout::LEADING); |
| } |
| |
| BubbleHeaderView::~BubbleHeaderView() {} |
| |
| void BubbleHeaderView::SetDetails(const base::string16& details_text) { |
| std::vector<base::string16> subst; |
| subst.push_back(details_text); |
| subst.push_back(l10n_util::GetStringUTF16(IDS_LEARN_MORE)); |
| |
| std::vector<size_t> offsets; |
| |
| base::string16 text = base::ReplaceStringPlaceholders( |
| base::ASCIIToUTF16("$1 $2"), subst, &offsets); |
| security_details_label_->SetText(text); |
| gfx::Range details_range(offsets[1], text.length()); |
| |
| views::StyledLabel::RangeStyleInfo link_style = |
| views::StyledLabel::RangeStyleInfo::CreateForLink(); |
| link_style.disable_line_wrapping = false; |
| |
| security_details_label_->AddStyleRange(details_range, link_style); |
| } |
| |
| void BubbleHeaderView::AddResetDecisionsLabel() { |
| std::vector<base::string16> subst; |
| subst.push_back( |
| l10n_util::GetStringUTF16(IDS_PAGE_INFO_INVALID_CERTIFICATE_DESCRIPTION)); |
| subst.push_back(l10n_util::GetStringUTF16( |
| IDS_PAGE_INFO_RESET_INVALID_CERTIFICATE_DECISIONS_BUTTON)); |
| |
| std::vector<size_t> offsets; |
| |
| base::string16 text = base::ReplaceStringPlaceholders( |
| base::ASCIIToUTF16("$1 $2"), subst, &offsets); |
| reset_cert_decisions_label_ = |
| new views::StyledLabel(text, styled_label_listener_); |
| reset_cert_decisions_label_->set_id( |
| PageInfoBubbleView::VIEW_ID_PAGE_INFO_LABEL_RESET_CERTIFICATE_DECISIONS); |
| gfx::Range link_range(offsets[1], text.length()); |
| |
| views::StyledLabel::RangeStyleInfo link_style = |
| views::StyledLabel::RangeStyleInfo::CreateForLink(); |
| link_style.disable_line_wrapping = false; |
| |
| reset_cert_decisions_label_->AddStyleRange(link_range, link_style); |
| // Fit the styled label to occupy available width. |
| reset_cert_decisions_label_->SizeToFit(0); |
| reset_decisions_label_container_->AddChildView(reset_cert_decisions_label_); |
| |
| // Now that it contains a label, the container needs padding at the top. |
| reset_decisions_label_container_->SetBorder(views::CreateEmptyBorder( |
| 8, views::GridLayout::kFixedSize, views::GridLayout::kFixedSize, 0)); |
| |
| InvalidateLayout(); |
| } |
| |
| void BubbleHeaderView::AddPasswordReuseButtons() { |
| change_password_button_ = views::MdTextButton::CreateSecondaryUiBlueButton( |
| button_listener_, |
| l10n_util::GetStringUTF16(IDS_PAGE_INFO_CHANGE_PASSWORD_BUTTON)); |
| change_password_button_->set_id( |
| PageInfoBubbleView::VIEW_ID_PAGE_INFO_BUTTON_CHANGE_PASSWORD); |
| whitelist_password_reuse_button_ = |
| views::MdTextButton::CreateSecondaryUiButton( |
| button_listener_, l10n_util::GetStringUTF16( |
| IDS_PAGE_INFO_WHITELIST_PASSWORD_REUSE_BUTTON)); |
| whitelist_password_reuse_button_->set_id( |
| PageInfoBubbleView::VIEW_ID_PAGE_INFO_BUTTON_WHITELIST_PASSWORD_REUSE); |
| |
| int kSpacingBetweenButtons = 8; |
| |
| // If these two buttons cannot fit into a single line, stack them vertically. |
| bool can_fit_in_one_line = |
| (password_reuse_button_container_->width() - kSpacingBetweenButtons) >= |
| (change_password_button_->CalculatePreferredSize().width() + |
| whitelist_password_reuse_button_->CalculatePreferredSize().width()); |
| auto layout = std::make_unique<views::BoxLayout>( |
| can_fit_in_one_line ? views::BoxLayout::kHorizontal |
| : views::BoxLayout::kVertical, |
| gfx::Insets(), kSpacingBetweenButtons); |
| // Make buttons left-aligned. For RTL languages, buttons will automatically |
| // become right-aligned. |
| layout->set_main_axis_alignment(views::BoxLayout::MAIN_AXIS_ALIGNMENT_START); |
| password_reuse_button_container_->SetLayoutManager(std::move(layout)); |
| |
| #if defined(OS_WIN) || defined(OS_CHROMEOS) |
| password_reuse_button_container_->AddChildView(change_password_button_); |
| password_reuse_button_container_->AddChildView( |
| whitelist_password_reuse_button_); |
| #else |
| password_reuse_button_container_->AddChildView( |
| whitelist_password_reuse_button_); |
| password_reuse_button_container_->AddChildView(change_password_button_); |
| #endif |
| |
| // Add padding at the top. |
| password_reuse_button_container_->SetBorder( |
| views::CreateEmptyBorder(8, views::GridLayout::kFixedSize, 0, 0)); |
| |
| InvalidateLayout(); |
| } |
| |
| //////////////////////////////////////////////////////////////////////////////// |
| // InternalPageInfoBubbleView |
| //////////////////////////////////////////////////////////////////////////////// |
| |
| InternalPageInfoBubbleView::InternalPageInfoBubbleView( |
| views::View* anchor_view, |
| const gfx::Rect& anchor_rect, |
| gfx::NativeView parent_window, |
| content::WebContents* web_contents, |
| const GURL& url) |
| : PageInfoBubbleViewBase(anchor_view, |
| anchor_rect, |
| parent_window, |
| PageInfoBubbleViewBase::BUBBLE_INTERNAL_PAGE, |
| web_contents) { |
| int text = IDS_PAGE_INFO_INTERNAL_PAGE; |
| if (url.SchemeIs(extensions::kExtensionScheme)) { |
| text = IDS_PAGE_INFO_EXTENSION_PAGE; |
| } else if (url.SchemeIs(content::kViewSourceScheme)) { |
| text = IDS_PAGE_INFO_VIEW_SOURCE_PAGE; |
| } else if (url.SchemeIs(url::kFileScheme)) { |
| text = IDS_PAGE_INFO_FILE_PAGE; |
| } else if (!url.SchemeIs(content::kChromeUIScheme) && |
| !url.SchemeIs(content::kChromeDevToolsScheme)) { |
| NOTREACHED(); |
| } |
| |
| // Title insets assume there is content (and thus have no bottom padding). Use |
| // dialog insets to get the bottom margin back. |
| set_title_margins( |
| ChromeLayoutProvider::Get()->GetInsetsMetric(views::INSETS_DIALOG)); |
| set_margins(gfx::Insets()); |
| |
| set_window_title(l10n_util::GetStringUTF16(text)); |
| |
| views::BubbleDialogDelegateView::CreateBubble(this); |
| |
| // Use a normal label's style for the title since there is no content. |
| views::Label* title_label = |
| static_cast<views::Label*>(GetBubbleFrameView()->title()); |
| title_label->SetFontList(views::Label::GetDefaultFontList()); |
| title_label->SetMultiLine(false); |
| title_label->SetElideBehavior(gfx::NO_ELIDE); |
| |
| SizeToContents(); |
| } |
| |
| InternalPageInfoBubbleView::~InternalPageInfoBubbleView() {} |
| |
| //////////////////////////////////////////////////////////////////////////////// |
| // PageInfoBubbleView |
| //////////////////////////////////////////////////////////////////////////////// |
| |
| PageInfoBubbleView::~PageInfoBubbleView() {} |
| |
| // static |
| views::BubbleDialogDelegateView* PageInfoBubbleView::CreatePageInfoBubble( |
| views::View* anchor_view, |
| const gfx::Rect& anchor_rect, |
| gfx::NativeWindow parent_window, |
| Profile* profile, |
| content::WebContents* web_contents, |
| const GURL& url, |
| const security_state::SecurityInfo& security_info) { |
| gfx::NativeView parent_view = platform_util::GetViewForWindow(parent_window); |
| |
| if (url.SchemeIs(content::kChromeUIScheme) || |
| url.SchemeIs(content::kChromeDevToolsScheme) || |
| url.SchemeIs(extensions::kExtensionScheme) || |
| url.SchemeIs(content::kViewSourceScheme) || |
| url.SchemeIs(url::kFileScheme)) { |
| return new InternalPageInfoBubbleView(anchor_view, anchor_rect, parent_view, |
| web_contents, url); |
| } |
| |
| return new PageInfoBubbleView(anchor_view, anchor_rect, parent_view, profile, |
| web_contents, url, security_info); |
| } |
| |
| PageInfoBubbleView::PageInfoBubbleView( |
| views::View* anchor_view, |
| const gfx::Rect& anchor_rect, |
| gfx::NativeView parent_window, |
| Profile* profile, |
| content::WebContents* web_contents, |
| const GURL& url, |
| const security_state::SecurityInfo& security_info) |
| : PageInfoBubbleViewBase(anchor_view, |
| anchor_rect, |
| parent_window, |
| PageInfoBubbleViewBase::BUBBLE_PAGE_INFO, |
| web_contents), |
| profile_(profile), |
| header_(nullptr), |
| site_settings_view_(nullptr), |
| cookie_button_(nullptr), |
| weak_factory_(this) { |
| // Capture the default bubble margin, and move it to the Layout classes. This |
| // is necessary so that the views::Separator can extend the full width of the |
| // bubble. |
| const int side_margin = margins().left(); |
| DCHECK_EQ(margins().left(), margins().right()); |
| |
| ChromeLayoutProvider* layout_provider = ChromeLayoutProvider::Get(); |
| |
| // In Harmony, the last view is a HoverButton, which overrides the bottom |
| // dialog inset in favor of its own. Note the multi-button value is used here |
| // assuming that the "Cookies" & "Site settings" buttons will always be shown. |
| const int hover_list_spacing = |
| layout_provider->GetDistanceMetric(DISTANCE_CONTENT_LIST_VERTICAL_MULTI); |
| const int bottom_margin = hover_list_spacing; |
| set_margins(gfx::Insets(margins().top(), 0, bottom_margin, 0)); |
| |
| views::GridLayout* layout = |
| SetLayoutManager(std::make_unique<views::GridLayout>(this)); |
| constexpr int kColumnId = 0; |
| views::ColumnSet* column_set = layout->AddColumnSet(kColumnId); |
| column_set->AddColumn(views::GridLayout::FILL, views::GridLayout::FILL, 1.0, |
| views::GridLayout::USE_PREF, 0, 0); |
| |
| layout->StartRow(views::GridLayout::kFixedSize, kColumnId); |
| header_ = new BubbleHeaderView(this, this, side_margin); |
| layout->AddView(header_); |
| |
| layout->StartRow(views::GridLayout::kFixedSize, kColumnId); |
| permissions_view_ = new views::View; |
| layout->AddView(permissions_view_); |
| |
| layout->StartRow(views::GridLayout::kFixedSize, kColumnId); |
| layout->AddView(new views::Separator()); |
| |
| layout->StartRowWithPadding(views::GridLayout::kFixedSize, kColumnId, |
| views::GridLayout::kFixedSize, |
| hover_list_spacing); |
| site_settings_view_ = CreateSiteSettingsView(); |
| layout->AddView(site_settings_view_); |
| |
| layout->StartRowWithPadding(views::GridLayout::kFixedSize, kColumnId, |
| views::GridLayout::kFixedSize, 0); |
| if (!profile->IsGuestSession()) |
| layout->AddView(CreateSiteSettingsLink(side_margin, this).release()); |
| |
| views::BubbleDialogDelegateView::CreateBubble(this); |
| presenter_.reset(new PageInfo( |
| this, profile, TabSpecificContentSettings::FromWebContents(web_contents), |
| web_contents, url, security_info)); |
| } |
| |
| void PageInfoBubbleView::WebContentsDestroyed() { |
| weak_factory_.InvalidateWeakPtrs(); |
| } |
| |
| void PageInfoBubbleView::OnPermissionChanged( |
| const PageInfoUI::PermissionInfo& permission) { |
| presenter_->OnSitePermissionChanged(permission.type, permission.setting); |
| // The menu buttons for the permissions might have longer strings now, so we |
| // need to layout and size the whole bubble. |
| Layout(); |
| SizeToContents(); |
| } |
| |
| void PageInfoBubbleView::OnChosenObjectDeleted( |
| const PageInfoUI::ChosenObjectInfo& info) { |
| presenter_->OnSiteChosenObjectDeleted(info.ui_info, |
| info.chooser_object->value); |
| } |
| |
| void PageInfoBubbleView::OnWidgetDestroying(views::Widget* widget) { |
| PageInfoBubbleViewBase::OnWidgetDestroying(widget); |
| presenter_->OnUIClosing(); |
| |
| // If we're closing the bubble because the user pressed ESC or because the |
| // user clicked Close (rather than the user clicking directly on something |
| // else), we should refocus the Omnibox. This lets the user tab into the |
| // "You should reload this page" infobar rather than dumping them back out |
| // into a stale webpage. |
| const views::Widget::ClosedReason closed_reason = |
| GetWidget()->closed_reason(); |
| if (closed_reason == views::Widget::ClosedReason::kEscKeyPressed || |
| closed_reason == views::Widget::ClosedReason::kCloseButtonClicked) { |
| // Because of how this bubble shows, the anchor is always in the toolbar, |
| // which means the infobar with the reload prompt is just after in the focus |
| // order. |
| View* const anchor = GetAnchorView(); |
| if (anchor) |
| anchor->GetFocusManager()->SetFocusedView(anchor); |
| } |
| } |
| |
| void PageInfoBubbleView::ButtonPressed(views::Button* button, |
| const ui::Event& event) { |
| switch (button->id()) { |
| case PageInfoBubbleView::VIEW_ID_PAGE_INFO_BUTTON_CLOSE: |
| GetWidget()->Close(); |
| break; |
| case PageInfoBubbleView::VIEW_ID_PAGE_INFO_BUTTON_CHANGE_PASSWORD: |
| presenter_->OnChangePasswordButtonPressed(web_contents()); |
| break; |
| case PageInfoBubbleView::VIEW_ID_PAGE_INFO_BUTTON_WHITELIST_PASSWORD_REUSE: |
| GetWidget()->Close(); |
| presenter_->OnWhitelistPasswordReuseButtonPressed(web_contents()); |
| break; |
| case PageInfoBubbleView::VIEW_ID_PAGE_INFO_LINK_OR_BUTTON_SITE_SETTINGS: |
| case PageInfoBubbleView::VIEW_ID_PAGE_INFO_LINK_OR_BUTTON_COOKIE_DIALOG: |
| case PageInfoBubbleView:: |
| VIEW_ID_PAGE_INFO_LINK_OR_BUTTON_CERTIFICATE_VIEWER: |
| HandleMoreInfoRequest(button); |
| break; |
| default: |
| NOTREACHED(); |
| } |
| } |
| |
| void PageInfoBubbleView::LinkClicked(views::Link* source, int event_flags) { |
| HandleMoreInfoRequest(source); |
| } |
| |
| gfx::Size PageInfoBubbleView::CalculatePreferredSize() const { |
| if (header_ == nullptr && site_settings_view_ == nullptr) { |
| return views::View::CalculatePreferredSize(); |
| } |
| |
| int height = views::View::CalculatePreferredSize().height(); |
| int width = kMinBubbleWidth; |
| if (site_settings_view_) { |
| width = std::max(width, permissions_view_->GetPreferredSize().width()); |
| } |
| width = std::min(width, kMaxBubbleWidth); |
| return gfx::Size(width, height); |
| } |
| |
| void PageInfoBubbleView::SetCookieInfo(const CookieInfoList& cookie_info_list) { |
| // Calculate the number of cookies used by this site. |cookie_info_list| |
| // should only ever have 2 items: first- and third-party cookies. |
| DCHECK_EQ(cookie_info_list.size(), 2u); |
| int total_allowed = 0; |
| for (const auto& i : cookie_info_list) { |
| total_allowed += i.allowed; |
| } |
| |
| // Get the string to display the number of cookies. |
| const base::string16 num_cookies_text = l10n_util::GetPluralStringFUTF16( |
| IDS_PAGE_INFO_NUM_COOKIES_PARENTHESIZED, total_allowed); |
| |
| // Create the cookie button if it doesn't yet exist. This method gets called |
| // each time site data is updated, so if it *does* already exist, skip this |
| // part and just update the text. |
| if (cookie_button_ == nullptr) { |
| // Get the icon. |
| PageInfoUI::PermissionInfo info; |
| info.type = CONTENT_SETTINGS_TYPE_COOKIES; |
| info.setting = CONTENT_SETTING_ALLOW; |
| info.is_incognito = |
| Profile::FromBrowserContext(web_contents()->GetBrowserContext()) |
| ->IsOffTheRecord(); |
| const gfx::ImageSkia icon = |
| PageInfoUI::GetPermissionIcon(info, GetRelatedTextColor()); |
| |
| const base::string16& tooltip = |
| l10n_util::GetStringUTF16(IDS_PAGE_INFO_COOKIES_TOOLTIP); |
| |
| cookie_button_ = |
| CreateMoreInfoButton( |
| this, icon, IDS_PAGE_INFO_COOKIES_BUTTON_TEXT, num_cookies_text, |
| VIEW_ID_PAGE_INFO_LINK_OR_BUTTON_COOKIE_DIALOG, tooltip) |
| .release(); |
| site_settings_view_->AddChildView(cookie_button_); |
| } |
| |
| // Update the text displaying the number of allowed cookies. |
| base::string16 button_text; |
| gfx::Range styled_range = GetRangeForFormatString( |
| IDS_PAGE_INFO_COOKIES_BUTTON_TEXT, num_cookies_text, &button_text); |
| cookie_button_->SetTitleTextWithHintRange(button_text, styled_range); |
| |
| Layout(); |
| SizeToContents(); |
| } |
| |
| void PageInfoBubbleView::SetPermissionInfo( |
| const PermissionInfoList& permission_info_list, |
| ChosenObjectInfoList chosen_object_info_list) { |
| // This method is called when Page Info is constructed/displayed, then called |
| // again whenever permissions/chosen objects change while the bubble is still |
| // opened. Once Page Info is displaying a non-zero number of permissions, all |
| // future calls to this will return early, based on the assumption that |
| // permission rows won't need to be added or removed. Theoretically this |
| // assumption is incorrect and it is actually possible that the number of |
| // permission rows will need to change, but this should be an extremely rare |
| // case that can be recovered from by closing & reopening the bubble. |
| // TODO(patricialor): Investigate removing callsites to this method other than |
| // the constructor. |
| if (permissions_view_->has_children()) |
| return; |
| |
| views::GridLayout* layout = permissions_view_->SetLayoutManager( |
| std::make_unique<views::GridLayout>(permissions_view_)); |
| |
| ChromeLayoutProvider* layout_provider = ChromeLayoutProvider::Get(); |
| const int list_item_padding = |
| layout_provider->GetDistanceMetric(DISTANCE_CONTROL_LIST_VERTICAL); |
| if (!permission_info_list.empty() || !chosen_object_info_list.empty()) { |
| layout->AddPaddingRow(views::GridLayout::kFixedSize, list_item_padding); |
| } else { |
| // If nothing to show, just add padding above the separator and exit. |
| layout->AddPaddingRow(views::GridLayout::kFixedSize, |
| layout_provider->GetDistanceMetric( |
| views::DISTANCE_UNRELATED_CONTROL_VERTICAL)); |
| return; |
| } |
| |
| int side_margin = |
| layout_provider->GetInsetsMetric(views::INSETS_DIALOG).left(); |
| // A permissions row will have an icon, title, and combobox, with a padding |
| // column on either side to match the dialog insets. Note the combobox can be |
| // variable widths depending on the text inside. |
| // *----------------------------------------------* |
| // |++| Icon | Permission Title | Combobox |++| |
| // *----------------------------------------------* |
| views::ColumnSet* permissions_set = |
| layout->AddColumnSet(kPermissionColumnSetId); |
| permissions_set->AddPaddingColumn(views::GridLayout::kFixedSize, side_margin); |
| permissions_set->AddColumn(views::GridLayout::CENTER, |
| views::GridLayout::CENTER, |
| views::GridLayout::kFixedSize, |
| views::GridLayout::FIXED, kIconColumnWidth, 0); |
| permissions_set->AddPaddingColumn( |
| views::GridLayout::kFixedSize, |
| layout_provider->GetDistanceMetric( |
| views::DISTANCE_RELATED_LABEL_HORIZONTAL)); |
| permissions_set->AddColumn( |
| views::GridLayout::LEADING, views::GridLayout::CENTER, 1.0, |
| views::GridLayout::USE_PREF, views::GridLayout::kFixedSize, 0); |
| permissions_set->AddPaddingColumn( |
| views::GridLayout::kFixedSize, |
| layout_provider->GetDistanceMetric( |
| views::DISTANCE_RELATED_CONTROL_HORIZONTAL)); |
| permissions_set->AddColumn( |
| views::GridLayout::TRAILING, views::GridLayout::FILL, |
| views::GridLayout::kFixedSize, views::GridLayout::USE_PREF, |
| views::GridLayout::kFixedSize, 0); |
| permissions_set->AddPaddingColumn(views::GridLayout::kFixedSize, side_margin); |
| |
| // |ChosenObjectView| will layout itself, so just add the missing padding |
| // here. |
| constexpr int kChosenObjectSectionId = 1; |
| views::ColumnSet* chosen_object_set = |
| layout->AddColumnSet(kChosenObjectSectionId); |
| chosen_object_set->AddPaddingColumn(views::GridLayout::kFixedSize, |
| side_margin); |
| chosen_object_set->AddColumn(views::GridLayout::FILL, views::GridLayout::FILL, |
| 1.0, views::GridLayout::USE_PREF, |
| views::GridLayout::kFixedSize, 0); |
| chosen_object_set->AddPaddingColumn(views::GridLayout::kFixedSize, |
| side_margin); |
| |
| for (const auto& permission : permission_info_list) { |
| std::unique_ptr<PermissionSelectorRow> selector = |
| std::make_unique<PermissionSelectorRow>( |
| profile_, |
| web_contents() ? web_contents()->GetVisibleURL() |
| : GURL::EmptyGURL(), |
| permission, layout); |
| selector->AddObserver(this); |
| selector_rows_.push_back(std::move(selector)); |
| } |
| |
| // Ensure most comboboxes are the same width by setting them all to the widest |
| // combobox size, provided it does not exceed a maximum width. |
| // For selected options that are over the maximum width, allow them to assume |
| // their full width. If the combobox selection is changed, this may make the |
| // widths inconsistent again, but that is OK since the widths will be updated |
| // on the next time the bubble is opened. |
| const int maximum_width = ChromeLayoutProvider::Get()->GetDistanceMetric( |
| views::DISTANCE_BUTTON_MAX_LINKABLE_WIDTH); |
| int combobox_width = 0; |
| for (const auto& selector : selector_rows_) { |
| int curr_width = selector->GetComboboxWidth(); |
| if (maximum_width >= curr_width) |
| combobox_width = std::max(combobox_width, curr_width); |
| } |
| for (const auto& selector : selector_rows_) |
| selector->SetMinComboboxWidth(combobox_width); |
| |
| for (auto& object : chosen_object_info_list) { |
| // Since chosen objects are presented after permissions in the same list, |
| // make sure its height is the same as the permissions row's minimum height |
| // plus padding. |
| layout->StartRow( |
| 1.0, kChosenObjectSectionId, |
| PermissionSelectorRow::MinHeightForPermissionRow() + list_item_padding); |
| // The view takes ownership of the object info. |
| auto object_view = std::make_unique<ChosenObjectView>(std::move(object)); |
| object_view->AddObserver(this); |
| layout->AddView(object_view.release()); |
| } |
| layout->AddPaddingRow(views::GridLayout::kFixedSize, list_item_padding); |
| |
| layout->Layout(permissions_view_); |
| SizeToContents(); |
| } |
| |
| void PageInfoBubbleView::SetIdentityInfo(const IdentityInfo& identity_info) { |
| std::unique_ptr<PageInfoUI::SecurityDescription> security_description = |
| GetSecurityDescription(identity_info); |
| |
| // Set the bubble title, update the title label text, then apply color. |
| set_window_title(security_description->summary); |
| GetBubbleFrameView()->UpdateWindowTitle(); |
| int text_style = views::style::STYLE_PRIMARY; |
| switch (security_description->summary_style) { |
| case SecuritySummaryColor::RED: |
| text_style = STYLE_RED; |
| break; |
| case SecuritySummaryColor::GREEN: |
| text_style = STYLE_GREEN; |
| break; |
| } |
| static_cast<views::Label*>(GetBubbleFrameView()->title()) |
| ->SetEnabledColor(views::style::GetColor( |
| *this, views::style::CONTEXT_DIALOG_TITLE, text_style)); |
| |
| if (identity_info.certificate) { |
| certificate_ = identity_info.certificate; |
| |
| if (identity_info.show_ssl_decision_revoke_button) { |
| header_->AddResetDecisionsLabel(); |
| } |
| |
| // Show information about the page's certificate. |
| // The text of link to the Certificate Viewer varies depending on the |
| // validity of the Certificate. |
| const bool valid_identity = |
| (identity_info.identity_status != PageInfo::SITE_IDENTITY_STATUS_ERROR); |
| base::string16 tooltip; |
| if (valid_identity) { |
| tooltip = l10n_util::GetStringFUTF16( |
| IDS_PAGE_INFO_CERTIFICATE_VALID_LINK_TOOLTIP, |
| base::UTF8ToUTF16(certificate_->issuer().GetDisplayName())); |
| } else { |
| tooltip = l10n_util::GetStringUTF16( |
| IDS_PAGE_INFO_CERTIFICATE_INVALID_LINK_TOOLTIP); |
| } |
| |
| // Add the Certificate Section. |
| const gfx::ImageSkia icon = |
| PageInfoUI::GetCertificateIcon(GetRelatedTextColor()); |
| const base::string16 secondary_text = l10n_util::GetStringUTF16( |
| valid_identity ? IDS_PAGE_INFO_CERTIFICATE_VALID_PARENTHESIZED |
| : IDS_PAGE_INFO_CERTIFICATE_INVALID_PARENTHESIZED); |
| std::unique_ptr<HoverButton> certificate_button = CreateMoreInfoButton( |
| this, icon, IDS_PAGE_INFO_CERTIFICATE_BUTTON_TEXT, secondary_text, |
| VIEW_ID_PAGE_INFO_LINK_OR_BUTTON_CERTIFICATE_VIEWER, tooltip); |
| certificate_button->set_auto_compute_tooltip(false); |
| site_settings_view_->AddChildView(certificate_button.release()); |
| } |
| |
| if (identity_info.show_change_password_buttons) { |
| header_->AddPasswordReuseButtons(); |
| } |
| |
| header_->SetDetails(security_description->details); |
| |
| Layout(); |
| SizeToContents(); |
| } |
| |
| #if defined(SAFE_BROWSING_DB_LOCAL) |
| std::unique_ptr<PageInfoUI::SecurityDescription> |
| PageInfoBubbleView::CreateSecurityDescriptionForPasswordReuse( |
| bool is_enterprise_password) const { |
| std::unique_ptr<PageInfoUI::SecurityDescription> security_description( |
| new PageInfoUI::SecurityDescription()); |
| security_description->summary_style = SecuritySummaryColor::RED; |
| security_description->summary = |
| l10n_util::GetStringUTF16(IDS_PAGE_INFO_CHANGE_PASSWORD_SUMMARY); |
| security_description->details = |
| safe_browsing::ChromePasswordProtectionService:: |
| GetPasswordProtectionService(profile_) |
| ->GetWarningDetailText( |
| is_enterprise_password |
| ? safe_browsing::LoginReputationClientRequest:: |
| PasswordReuseEvent::ENTERPRISE_PASSWORD |
| : safe_browsing::LoginReputationClientRequest:: |
| PasswordReuseEvent::SIGN_IN_PASSWORD); |
| return security_description; |
| } |
| #endif |
| |
| views::View* PageInfoBubbleView::CreateSiteSettingsView() { |
| views::View* site_settings_view = new views::View(); |
| auto* box_layout = site_settings_view->SetLayoutManager( |
| std::make_unique<views::BoxLayout>(views::BoxLayout::kVertical)); |
| box_layout->set_cross_axis_alignment( |
| views::BoxLayout::CROSS_AXIS_ALIGNMENT_STRETCH); |
| |
| return site_settings_view; |
| } |
| |
| void PageInfoBubbleView::HandleMoreInfoRequest(views::View* source) { |
| // The bubble closes automatically when the collected cookies dialog or the |
| // certificate viewer opens. So delay handling of the link clicked to avoid |
| // a crash in the base class which needs to complete the mouse event handling. |
| base::PostTaskWithTraits( |
| FROM_HERE, {content::BrowserThread::UI}, |
| base::BindOnce(&PageInfoBubbleView::HandleMoreInfoRequestAsync, |
| weak_factory_.GetWeakPtr(), source->id())); |
| } |
| |
| void PageInfoBubbleView::HandleMoreInfoRequestAsync(int view_id) { |
| // All switch cases require accessing web_contents(), so we check it here. |
| if (web_contents() == nullptr || web_contents()->IsBeingDestroyed()) { |
| return; |
| } |
| switch (view_id) { |
| case PageInfoBubbleView::VIEW_ID_PAGE_INFO_LINK_OR_BUTTON_SITE_SETTINGS: |
| presenter_->OpenSiteSettingsView(); |
| break; |
| case PageInfoBubbleView::VIEW_ID_PAGE_INFO_LINK_OR_BUTTON_COOKIE_DIALOG: |
| // Count how often the Collected Cookies dialog is opened. |
| presenter_->RecordPageInfoAction( |
| PageInfo::PAGE_INFO_COOKIES_DIALOG_OPENED); |
| new CollectedCookiesViews(web_contents()); |
| break; |
| case PageInfoBubbleView:: |
| VIEW_ID_PAGE_INFO_LINK_OR_BUTTON_CERTIFICATE_VIEWER: { |
| gfx::NativeWindow top_window = web_contents()->GetTopLevelNativeWindow(); |
| if (certificate_ && top_window) { |
| presenter_->RecordPageInfoAction( |
| PageInfo::PAGE_INFO_CERTIFICATE_DIALOG_OPENED); |
| ShowCertificateViewer(web_contents(), top_window, certificate_.get()); |
| } |
| break; |
| } |
| default: |
| NOTREACHED(); |
| } |
| } |
| |
| void PageInfoBubbleView::StyledLabelLinkClicked(views::StyledLabel* label, |
| const gfx::Range& range, |
| int event_flags) { |
| switch (label->id()) { |
| case PageInfoBubbleView::VIEW_ID_PAGE_INFO_LABEL_SECURITY_DETAILS: |
| web_contents()->OpenURL(content::OpenURLParams( |
| GURL(chrome::kPageInfoHelpCenterURL), content::Referrer(), |
| WindowOpenDisposition::NEW_FOREGROUND_TAB, ui::PAGE_TRANSITION_LINK, |
| false)); |
| presenter_->RecordPageInfoAction( |
| PageInfo::PAGE_INFO_CONNECTION_HELP_OPENED); |
| break; |
| case PageInfoBubbleView:: |
| VIEW_ID_PAGE_INFO_LABEL_RESET_CERTIFICATE_DECISIONS: |
| presenter_->OnRevokeSSLErrorBypassButtonPressed(); |
| GetWidget()->Close(); |
| break; |
| default: |
| NOTREACHED(); |
| } |
| } |
| |
| void ShowPageInfoDialogImpl(Browser* browser, |
| content::WebContents* web_contents, |
| const GURL& virtual_url, |
| const security_state::SecurityInfo& security_info, |
| bubble_anchor_util::Anchor anchor) { |
| AnchorConfiguration configuration = |
| GetPageInfoAnchorConfiguration(browser, anchor); |
| gfx::Rect anchor_rect = |
| configuration.anchor_view ? gfx::Rect() : GetPageInfoAnchorRect(browser); |
| gfx::NativeWindow parent_window = browser->window()->GetNativeWindow(); |
| views::BubbleDialogDelegateView* bubble = |
| PageInfoBubbleView::CreatePageInfoBubble( |
| configuration.anchor_view, anchor_rect, parent_window, |
| browser->profile(), web_contents, virtual_url, security_info); |
| bubble->SetHighlightedButton(configuration.highlighted_button); |
| bubble->SetArrow(configuration.bubble_arrow); |
| bubble->GetWidget()->Show(); |
| } |