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