1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 #include "chrome/browser/ui/views/location_bar/location_icon_view.h"
6 
7 #include "base/bind.h"
8 #include "chrome/browser/extensions/extension_ui_util.h"
9 #include "chrome/browser/ui/layout_constants.h"
10 #include "chrome/browser/ui/page_info/page_info_dialog.h"
11 #include "chrome/browser/ui/view_ids.h"
12 #include "chrome/browser/ui/views/page_info/page_info_bubble_view.h"
13 #include "chrome/grit/chromium_strings.h"
14 #include "chrome/grit/generated_resources.h"
15 #include "components/dom_distiller/core/url_constants.h"
16 #include "components/omnibox/browser/omnibox_edit_model.h"
17 #include "components/omnibox/browser/omnibox_field_trial.h"
18 #include "components/security_state/core/security_state.h"
19 #include "components/strings/grit/components_strings.h"
20 #include "content/public/browser/web_contents.h"
21 #include "content/public/common/url_constants.h"
22 #include "extensions/common/constants.h"
23 #include "ui/accessibility/ax_enums.mojom.h"
24 #include "ui/accessibility/ax_node_data.h"
25 #include "ui/base/clipboard/clipboard.h"
26 #include "ui/base/l10n/l10n_util.h"
27 #include "ui/views/controls/label.h"
28 
29 using content::WebContents;
30 using security_state::SecurityLevel;
31 
LocationIconView(const gfx::FontList & font_list,IconLabelBubbleView::Delegate * parent_delegate,Delegate * delegate)32 LocationIconView::LocationIconView(
33     const gfx::FontList& font_list,
34     IconLabelBubbleView::Delegate* parent_delegate,
35     Delegate* delegate)
36     : IconLabelBubbleView(font_list, parent_delegate), delegate_(delegate) {
37   DCHECK(delegate_);
38 
39   SetID(VIEW_ID_LOCATION_ICON);
40   SetUpForAnimation();
41 
42   // Readability is guaranteed by the omnibox theme.
43   label()->SetAutoColorReadabilityEnabled(false);
44 }
45 
~LocationIconView()46 LocationIconView::~LocationIconView() {}
47 
GetMinimumSize() const48 gfx::Size LocationIconView::GetMinimumSize() const {
49   return GetMinimumSizeForPreferredSize(GetPreferredSize());
50 }
51 
OnMouseDragged(const ui::MouseEvent & event)52 bool LocationIconView::OnMouseDragged(const ui::MouseEvent& event) {
53   delegate_->OnLocationIconDragged(event);
54   return IconLabelBubbleView::OnMouseDragged(event);
55 }
56 
GetForegroundColor() const57 SkColor LocationIconView::GetForegroundColor() const {
58   SecurityLevel security_level = SecurityLevel::NONE;
59   if (!delegate_->IsEditingOrEmpty())
60     security_level = delegate_->GetLocationBarModel()->GetSecurityLevel();
61 
62   return delegate_->GetSecurityChipColor(security_level);
63 }
64 
ShouldShowSeparator() const65 bool LocationIconView::ShouldShowSeparator() const {
66   return ShouldShowLabel();
67 }
68 
ShowBubble(const ui::Event & event)69 bool LocationIconView::ShowBubble(const ui::Event& event) {
70   return delegate_->ShowPageInfoDialog();
71 }
72 
IsBubbleShowing() const73 bool LocationIconView::IsBubbleShowing() const {
74   return PageInfoBubbleView::GetShownBubbleType() !=
75          PageInfoBubbleView::BUBBLE_NONE;
76 }
77 
OnMousePressed(const ui::MouseEvent & event)78 bool LocationIconView::OnMousePressed(const ui::MouseEvent& event) {
79   IconLabelBubbleView::OnMousePressed(event);
80   delegate_->OnLocationIconPressed(event);
81   // Since OnLocationIconPressed() runs a nested message loop on Linux, |this|
82   // may be deleted by now.
83   return true;
84 }
85 
GetAccessibleNodeData(ui::AXNodeData * node_data)86 void LocationIconView::GetAccessibleNodeData(ui::AXNodeData* node_data) {
87   if (delegate_->IsEditingOrEmpty()) {
88     node_data->role = ax::mojom::Role::kImage;
89     node_data->SetName(l10n_util::GetStringUTF8(IDS_ACC_SEARCH_ICON));
90     return;
91   }
92 
93   // If no display text exists, ensure that the accessibility label is added.
94   auto accessibility_label = base::UTF16ToUTF8(
95       delegate_->GetLocationBarModel()->GetSecureAccessibilityText());
96   if (label()->GetText().empty() && !accessibility_label.empty()) {
97     node_data->AddStringAttribute(ax::mojom::StringAttribute::kDescription,
98                                   accessibility_label);
99   }
100 
101   IconLabelBubbleView::GetAccessibleNodeData(node_data);
102   node_data->role = ax::mojom::Role::kPopUpButton;
103 }
104 
AddedToWidget()105 void LocationIconView::AddedToWidget() {
106   Update(true);
107 }
108 
OnThemeChanged()109 void LocationIconView::OnThemeChanged() {
110   IconLabelBubbleView::OnThemeChanged();
111   UpdateIcon();
112 }
113 
GetMinimumLabelTextWidth() const114 int LocationIconView::GetMinimumLabelTextWidth() const {
115   int width = 0;
116 
117   base::string16 text = GetText();
118   if (text == label()->GetText()) {
119     // Optimize this common case by not creating a new label.
120     // GetPreferredSize is not dependent on the label's current
121     // width, so this returns the same value as the branch below.
122     width = label()->GetPreferredSize().width();
123   } else {
124     views::Label label(text, {font_list()});
125     width = label.GetPreferredSize().width();
126   }
127   return GetMinimumSizeForPreferredSize(GetSizeForLabelWidth(width)).width();
128 }
129 
ShouldShowText() const130 bool LocationIconView::ShouldShowText() const {
131   if (delegate_->IsEditingOrEmpty())
132     return false;
133 
134   const auto* location_bar_model = delegate_->GetLocationBarModel();
135   const GURL& url = location_bar_model->GetURL();
136   if (url.SchemeIs(content::kChromeUIScheme) ||
137       url.SchemeIs(extensions::kExtensionScheme) ||
138       url.SchemeIs(url::kFileScheme) ||
139       url.SchemeIs(dom_distiller::kDomDistillerScheme)) {
140     return true;
141   }
142 
143   return !location_bar_model->GetSecureDisplayText().empty();
144 }
145 
get_ink_drop_for_testing()146 const views::InkDrop* LocationIconView::get_ink_drop_for_testing() {
147   return GetInkDrop();
148 }
149 
GetText() const150 base::string16 LocationIconView::GetText() const {
151   if (delegate_->IsEditingOrEmpty())
152     return base::string16();
153 
154   if (delegate_->GetLocationBarModel()->GetURL().SchemeIs(
155           content::kChromeUIScheme))
156     return l10n_util::GetStringUTF16(IDS_SHORT_PRODUCT_NAME);
157 
158   if (delegate_->GetLocationBarModel()->GetURL().SchemeIs(url::kFileScheme))
159     return l10n_util::GetStringUTF16(IDS_OMNIBOX_FILE);
160 
161   if (delegate_->GetLocationBarModel()->GetURL().SchemeIs(
162           dom_distiller::kDomDistillerScheme)) {
163     return l10n_util::GetStringUTF16(IDS_OMNIBOX_READER_MODE);
164   }
165 
166   if (delegate_->GetWebContents()) {
167     // On ChromeOS, this can be called using web_contents from
168     // SimpleWebViewDialog::GetWebContents() which always returns null.
169     // TODO(crbug.com/680329) Remove the null check and make
170     // SimpleWebViewDialog::GetWebContents return the proper web contents
171     // instead.
172     const base::string16 extension_name =
173         extensions::ui_util::GetEnabledExtensionNameForUrl(
174             delegate_->GetLocationBarModel()->GetURL(),
175             delegate_->GetWebContents()->GetBrowserContext());
176     if (!extension_name.empty())
177       return extension_name;
178   }
179 
180   return delegate_->GetLocationBarModel()->GetSecureDisplayText();
181 }
182 
ShouldAnimateTextVisibilityChange() const183 bool LocationIconView::ShouldAnimateTextVisibilityChange() const {
184   if (delegate_->IsEditingOrEmpty())
185     return false;
186 
187   SecurityLevel level = delegate_->GetLocationBarModel()->GetSecurityLevel();
188   // Do not animate transitions from WARNING to DANGEROUS, since
189   // the transition can look confusing/messy.
190   if (level == SecurityLevel::DANGEROUS &&
191       last_update_security_level_ == SecurityLevel::WARNING)
192     return false;
193   return (level == SecurityLevel::DANGEROUS || level == SecurityLevel::WARNING);
194 }
195 
UpdateTextVisibility(bool suppress_animations)196 void LocationIconView::UpdateTextVisibility(bool suppress_animations) {
197   SetLabel(GetText());
198 
199   bool should_show = ShouldShowText();
200   if (!ShouldAnimateTextVisibilityChange() || suppress_animations)
201     ResetSlideAnimation(should_show);
202   else if (should_show)
203     AnimateIn(base::nullopt);
204   else
205     AnimateOut();
206 }
207 
UpdateIcon()208 void LocationIconView::UpdateIcon() {
209   // Cancel any previous outstanding icon requests, as they are now outdated.
210   icon_fetch_weak_ptr_factory_.InvalidateWeakPtrs();
211 
212   ui::ImageModel icon = delegate_->GetLocationIcon(
213       base::BindOnce(&LocationIconView::OnIconFetched,
214                      icon_fetch_weak_ptr_factory_.GetWeakPtr()));
215   if (!icon.IsEmpty())
216     SetImageModel(icon);
217 }
218 
OnIconFetched(const gfx::Image & image)219 void LocationIconView::OnIconFetched(const gfx::Image& image) {
220   DCHECK(!image.IsEmpty());
221   SetImageModel(ui::ImageModel::FromImage(image));
222 }
223 
Update(bool suppress_animations)224 void LocationIconView::Update(bool suppress_animations) {
225   UpdateTextVisibility(suppress_animations);
226   UpdateIcon();
227 
228   // The label text color may have changed in response to changes in security
229   // level.
230   UpdateLabelColors();
231 
232   bool is_editing_or_empty = delegate_->IsEditingOrEmpty();
233   // The tooltip should be shown if we are not editing or empty.
234   SetTooltipText(is_editing_or_empty
235                      ? base::string16()
236                      : l10n_util::GetStringUTF16(IDS_TOOLTIP_LOCATION_ICON));
237 
238   // We should only enable/disable the InkDrop if the editing state has changed,
239   // as the drop gets recreated when SetInkDropMode is called. This can result
240   // in strange behaviour, like the the InkDrop disappearing mid animation.
241   if (is_editing_or_empty != was_editing_or_empty_) {
242     // If the omnibox is empty or editing, the user should not be able to left
243     // click on the icon. As such, the icon should not show a highlight or be
244     // focusable. Note: using the middle mouse to copy-and-paste should still
245     // work on the icon.
246     if (is_editing_or_empty) {
247       SetInkDropMode(InkDropMode::OFF);
248       SetFocusBehavior(FocusBehavior::NEVER);
249     } else {
250       SetInkDropMode(InkDropMode::ON);
251 
252 #if defined(OS_MAC)
253       SetFocusBehavior(FocusBehavior::ACCESSIBLE_ONLY);
254 #else
255       SetFocusBehavior(FocusBehavior::ALWAYS);
256 #endif
257     }
258   }
259 
260   last_update_security_level_ = SecurityLevel::NONE;
261   if (!is_editing_or_empty) {
262     last_update_security_level_ =
263         delegate_->GetLocationBarModel()->GetSecurityLevel();
264   }
265   was_editing_or_empty_ = is_editing_or_empty;
266 }
267 
IsTriggerableEvent(const ui::Event & event)268 bool LocationIconView::IsTriggerableEvent(const ui::Event& event) {
269   if (delegate_->IsEditingOrEmpty())
270     return false;
271 
272   if (event.IsMouseEvent()) {
273     if (event.AsMouseEvent()->IsOnlyMiddleMouseButton())
274       return false;
275   } else if (event.IsGestureEvent() && event.type() != ui::ET_GESTURE_TAP) {
276     return false;
277   }
278 
279   return IconLabelBubbleView::IsTriggerableEvent(event);
280 }
281 
GetMinimumSizeForPreferredSize(gfx::Size size) const282 gfx::Size LocationIconView::GetMinimumSizeForPreferredSize(
283     gfx::Size size) const {
284   const int kMinCharacters = 10;
285   size.SetToMin(
286       GetSizeForLabelWidth(font_list().GetExpectedTextWidth(kMinCharacters)));
287   return size;
288 }
289