1 // Copyright 2014 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "chrome/browser/ui/views/location_bar/origin_chip_view.h"
7 #include "base/files/file_path.h"
8 #include "base/metrics/histogram.h"
9 #include "base/strings/string_util.h"
10 #include "base/strings/utf_string_conversions.h"
11 #include "chrome/browser/browser_process.h"
12 #include "chrome/browser/extensions/extension_icon_image.h"
13 #include "chrome/browser/extensions/extension_service.h"
14 #include "chrome/browser/extensions/extension_util.h"
15 #include "chrome/browser/favicon/favicon_tab_helper.h"
16 #include "chrome/browser/profiles/profile.h"
17 #include "chrome/browser/safe_browsing/safe_browsing_service.h"
18 #include "chrome/browser/safe_browsing/ui_manager.h"
19 #include "chrome/browser/search/search.h"
20 #include "chrome/browser/themes/theme_properties.h"
21 #include "chrome/browser/ui/browser.h"
22 #include "chrome/browser/ui/elide_url.h"
23 #include "chrome/browser/ui/omnibox/omnibox_view.h"
24 #include "chrome/browser/ui/toolbar/origin_chip_info.h"
25 #include "chrome/browser/ui/toolbar/toolbar_model.h"
26 #include "chrome/browser/ui/views/location_bar/location_bar_view.h"
27 #include "chrome/common/extensions/extension_constants.h"
28 #include "content/public/browser/user_metrics.h"
29 #include "content/public/browser/web_contents.h"
30 #include "content/public/common/url_constants.h"
31 #include "extensions/browser/extension_system.h"
32 #include "extensions/common/constants.h"
33 #include "extensions/common/manifest_handlers/icons_handler.h"
34 #include "grit/generated_resources.h"
35 #include "grit/theme_resources.h"
36 #include "ui/base/l10n/l10n_util.h"
37 #include "ui/base/resource/resource_bundle.h"
38 #include "ui/base/theme_provider.h"
39 #include "ui/gfx/animation/slide_animation.h"
40 #include "ui/gfx/canvas.h"
41 #include "ui/gfx/font_list.h"
42 #include "ui/views/background.h"
43 #include "ui/views/controls/button/label_button.h"
44 #include "ui/views/controls/button/label_button_border.h"
45 #include "ui/views/painter.h"
48 // OriginChipExtensionIcon ----------------------------------------------------
50 class OriginChipExtensionIcon : public extensions::IconImage::Observer {
52 OriginChipExtensionIcon(LocationIconView* icon_view,
54 const extensions::Extension* extension);
55 virtual ~OriginChipExtensionIcon();
57 // IconImage::Observer:
58 virtual void OnExtensionIconImageChanged(
59 extensions::IconImage* image) OVERRIDE;
62 LocationIconView* icon_view_;
63 scoped_ptr<extensions::IconImage> icon_image_;
65 DISALLOW_COPY_AND_ASSIGN(OriginChipExtensionIcon);
68 OriginChipExtensionIcon::OriginChipExtensionIcon(
69 LocationIconView* icon_view,
71 const extensions::Extension* extension)
72 : icon_view_(icon_view),
73 icon_image_(new extensions::IconImage(
76 extensions::IconsInfo::GetIcons(extension),
77 extension_misc::EXTENSION_ICON_BITTY,
78 extensions::util::GetDefaultAppIcon(),
80 // Forces load of the image.
81 icon_image_->image_skia().GetRepresentation(1.0f);
83 if (!icon_image_->image_skia().image_reps().empty())
84 OnExtensionIconImageChanged(icon_image_.get());
87 OriginChipExtensionIcon::~OriginChipExtensionIcon() {
90 void OriginChipExtensionIcon::OnExtensionIconImageChanged(
91 extensions::IconImage* image) {
93 icon_view_->SetImage(&icon_image_->image_skia());
97 // OriginChipView -------------------------------------------------------------
101 const int kEdgeThickness = 5;
102 const int k16x16IconLeadingSpacing = 1;
103 const int k16x16IconTrailingSpacing = 2;
104 const int kIconTextSpacing = 3;
105 const int kTrailingLabelMargin = 0;
107 const int kNormalImages[3][9] = {
108 IMAGE_GRID(IDR_ORIGIN_CHIP_NORMAL),
109 IMAGE_GRID(IDR_ORIGIN_CHIP_HOVER),
110 IMAGE_GRID(IDR_ORIGIN_CHIP_PRESSED)
113 const int kMalwareImages[3][9] = {
114 IMAGE_GRID(IDR_ORIGIN_CHIP_MALWARE_NORMAL),
115 IMAGE_GRID(IDR_ORIGIN_CHIP_MALWARE_HOVER),
116 IMAGE_GRID(IDR_ORIGIN_CHIP_MALWARE_PRESSED)
119 const int kBrokenSSLImages[3][9] = {
120 IMAGE_GRID(IDR_ORIGIN_CHIP_BROKENSSL_NORMAL),
121 IMAGE_GRID(IDR_ORIGIN_CHIP_BROKENSSL_HOVER),
122 IMAGE_GRID(IDR_ORIGIN_CHIP_BROKENSSL_PRESSED)
125 const int kEVImages[3][9] = {
126 IMAGE_GRID(IDR_ORIGIN_CHIP_EV_NORMAL),
127 IMAGE_GRID(IDR_ORIGIN_CHIP_EV_HOVER),
128 IMAGE_GRID(IDR_ORIGIN_CHIP_EV_PRESSED)
133 OriginChipView::OriginChipView(LocationBarView* location_bar_view,
135 const gfx::FontList& font_list)
136 : LabelButton(this, base::string16()),
137 location_bar_view_(location_bar_view),
139 showing_16x16_icon_(false) {
140 scoped_refptr<SafeBrowsingService> sb_service =
141 g_browser_process->safe_browsing_service();
142 // May not be set for unit tests.
143 if (sb_service && sb_service->ui_manager())
144 sb_service->ui_manager()->AddObserver(this);
146 SetFontList(font_list);
148 image()->EnableCanvasFlippingForRTLUI(false);
150 // TODO(gbillock): Would be nice to just use stock LabelButton stuff here.
151 location_icon_view_ = new LocationIconView(location_bar_view_);
152 // Make location icon hover events count as hovering the origin chip.
153 location_icon_view_->set_interactive(false);
154 location_icon_view_->ShowTooltip(true);
155 AddChildView(location_icon_view_);
157 host_label_ = new views::Label(base::string16(), GetFontList());
158 AddChildView(host_label_);
160 fade_in_animation_.reset(new gfx::SlideAnimation(this));
161 fade_in_animation_->SetTweenType(gfx::Tween::LINEAR);
162 fade_in_animation_->SetSlideDuration(300);
165 OriginChipView::~OriginChipView() {
166 scoped_refptr<SafeBrowsingService> sb_service =
167 g_browser_process->safe_browsing_service();
168 if (sb_service.get() && sb_service->ui_manager())
169 sb_service->ui_manager()->RemoveObserver(this);
172 bool OriginChipView::ShouldShow() {
173 return chrome::ShouldDisplayOriginChipV2() &&
174 location_bar_view_->GetToolbarModel()->WouldOmitURLDueToOriginChip() &&
175 location_bar_view_->GetToolbarModel()->origin_chip_enabled();
178 void OriginChipView::Update(content::WebContents* web_contents) {
182 // Note: security level can change async as the connection is made.
183 GURL url = location_bar_view_->GetToolbarModel()->GetURL();
184 const ToolbarModel::SecurityLevel security_level =
185 location_bar_view_->GetToolbarModel()->GetSecurityLevel(true);
187 bool url_malware = OriginChip::IsMalware(url, web_contents);
189 // TODO(gbillock): We persist a malware setting while a new WebContents
190 // content is loaded, meaning that we end up transiently marking a safe
191 // page as malware. Need to fix that.
193 if ((url == url_displayed_) &&
194 (security_level == security_level_) &&
195 (url_malware == url_malware_))
198 url_displayed_ = url;
199 url_malware_ = url_malware;
200 security_level_ = security_level;
203 SetBorderImages(kMalwareImages);
204 } else if (security_level_ == ToolbarModel::SECURITY_ERROR) {
205 SetBorderImages(kBrokenSSLImages);
206 } else if (security_level_ == ToolbarModel::EV_SECURE) {
207 SetBorderImages(kEVImages);
209 SetBorderImages(kNormalImages);
212 base::string16 host =
213 OriginChip::LabelFromURLForProfile(url_displayed_, profile_);
214 if (security_level_ == ToolbarModel::EV_SECURE) {
215 host = l10n_util::GetStringFUTF16(IDS_SITE_CHIP_EV_SSL_LABEL,
216 location_bar_view_->GetToolbarModel()->GetEVCertName(),
219 host_label_->SetText(host);
220 host_label_->SetTooltipText(base::UTF8ToUTF16(url.spec()));
221 host_label_->SetElideBehavior(views::Label::NO_ELIDE);
223 int icon = location_bar_view_->GetToolbarModel()->GetIconForSecurityLevel(
225 showing_16x16_icon_ = false;
227 if (url_displayed_.is_empty() ||
228 url_displayed_.SchemeIs(content::kChromeUIScheme)) {
229 icon = IDR_PRODUCT_LOGO_16;
230 showing_16x16_icon_ = true;
233 location_icon_view_->SetImage(GetThemeProvider()->GetImageSkiaNamed(icon));
235 if (url_displayed_.SchemeIs(extensions::kExtensionScheme)) {
236 icon = IDR_EXTENSIONS_FAVICON;
237 showing_16x16_icon_ = true;
238 location_icon_view_->SetImage(GetThemeProvider()->GetImageSkiaNamed(icon));
240 ExtensionService* service =
241 extensions::ExtensionSystem::Get(profile_)->extension_service();
242 const extensions::Extension* extension =
243 service->extensions()->GetExtensionOrAppByURL(url_displayed_);
244 extension_icon_.reset(
245 new OriginChipExtensionIcon(location_icon_view_, profile_, extension));
247 extension_icon_.reset();
254 void OriginChipView::OnChanged() {
255 Update(location_bar_view_->GetWebContents());
256 // TODO(gbillock): Also need to potentially repaint infobars to make sure the
257 // arrows are pointing to the right spot. Only needed for some edge cases.
260 int OriginChipView::ElideDomainTarget(int target_max_width) {
261 base::string16 host =
262 OriginChip::LabelFromURLForProfile(url_displayed_, profile_);
263 host_label_->SetText(host);
264 int width = GetPreferredSize().width();
265 if (width <= target_max_width)
268 gfx::Size label_size = host_label_->GetPreferredSize();
269 int padding_width = width - label_size.width();
271 host_label_->SetText(ElideHost(
272 location_bar_view_->GetToolbarModel()->GetURL(),
273 host_label_->font_list(), target_max_width - padding_width));
274 return GetPreferredSize().width();
277 void OriginChipView::FadeIn() {
278 fade_in_animation_->Show();
281 gfx::Size OriginChipView::GetPreferredSize() {
282 gfx::Size label_size = host_label_->GetPreferredSize();
283 gfx::Size icon_size = location_icon_view_->GetPreferredSize();
284 int icon_spacing = showing_16x16_icon_ ?
285 (k16x16IconLeadingSpacing + k16x16IconTrailingSpacing) : 0;
286 return gfx::Size(kEdgeThickness + icon_size.width() + icon_spacing +
287 kIconTextSpacing + label_size.width() +
288 kTrailingLabelMargin + kEdgeThickness,
292 void OriginChipView::SetBorderImages(const int images[3][9]) {
293 scoped_ptr<views::LabelButtonBorder> border(
294 new views::LabelButtonBorder(views::Button::STYLE_BUTTON));
296 views::Painter* painter = views::Painter::CreateImageGridPainter(images[0]);
297 border->SetPainter(false, Button::STATE_NORMAL, painter);
298 painter = views::Painter::CreateImageGridPainter(images[1]);
299 border->SetPainter(false, Button::STATE_HOVERED, painter);
300 painter = views::Painter::CreateImageGridPainter(images[2]);
301 border->SetPainter(false, Button::STATE_PRESSED, painter);
303 SetBorder(border.PassAs<views::Border>());
305 // Calculate a representative background color of the provided image grid and
306 // set it as the background color of the host label in order to color the text
307 // appropriately. We grab the color of the middle pixel of the middle image
308 // of the background, which we treat as the representative color of the entire
309 // background (reasonable, given the current appearance of these images).
310 const SkBitmap& bitmap(
311 GetThemeProvider()->GetImageSkiaNamed(
312 images[0][4])->GetRepresentation(1.0f).sk_bitmap());
313 SkAutoLockPixels pixel_lock(bitmap);
314 host_label_->SetBackgroundColor(
315 bitmap.getColor(bitmap.width() / 2, bitmap.height() / 2));
318 void OriginChipView::AnimationProgressed(const gfx::Animation* animation) {
319 if (animation == fade_in_animation_.get())
322 views::LabelButton::AnimationProgressed(animation);
325 void OriginChipView::AnimationEnded(const gfx::Animation* animation) {
326 if (animation == fade_in_animation_.get())
327 fade_in_animation_->Reset();
329 views::LabelButton::AnimationEnded(animation);
332 void OriginChipView::Layout() {
333 // TODO(gbillock): Eventually we almost certainly want to use
334 // LocationBarLayout for leading and trailing decorations.
336 location_icon_view_->SetBounds(
337 kEdgeThickness + (showing_16x16_icon_ ? k16x16IconLeadingSpacing : 0),
338 LocationBarView::kNormalEdgeThickness,
339 location_icon_view_->GetPreferredSize().width(),
340 height() - 2 * LocationBarView::kNormalEdgeThickness);
342 int host_label_x = location_icon_view_->x() + location_icon_view_->width() +
344 host_label_x += showing_16x16_icon_ ? k16x16IconTrailingSpacing : 0;
345 int host_label_width =
346 width() - host_label_x - kEdgeThickness - kTrailingLabelMargin;
347 host_label_->SetBounds(host_label_x,
348 LocationBarView::kNormalEdgeThickness,
350 height() - 2 * LocationBarView::kNormalEdgeThickness);
353 void OriginChipView::OnPaintBorder(gfx::Canvas* canvas) {
354 if (fade_in_animation_->is_animating()) {
355 canvas->SaveLayerAlpha(static_cast<uint8>(
356 fade_in_animation_->CurrentValueBetween(0, 255)));
357 views::LabelButton::OnPaintBorder(canvas);
360 views::LabelButton::OnPaintBorder(canvas);
364 // TODO(gbillock): Make the LocationBarView or OmniboxView the listener for
366 void OriginChipView::ButtonPressed(views::Button* sender,
367 const ui::Event& event) {
368 // See if the event needs to be passed to the LocationIconView.
369 if (event.IsMouseEvent() || (event.type() == ui::ET_GESTURE_TAP)) {
370 location_icon_view_->set_interactive(true);
371 const ui::LocatedEvent& located_event =
372 static_cast<const ui::LocatedEvent&>(event);
373 if (GetEventHandlerForPoint(located_event.location()) ==
374 location_icon_view_) {
375 location_icon_view_->page_info_helper()->ProcessEvent(located_event);
376 location_icon_view_->set_interactive(false);
379 location_icon_view_->set_interactive(false);
382 UMA_HISTOGRAM_COUNTS("OriginChip.Pressed", 1);
383 content::RecordAction(base::UserMetricsAction("OriginChipPress"));
385 location_bar_view_->ShowURL();
388 // Note: When OnSafeBrowsingHit would be called, OnSafeBrowsingMatch will
389 // have already been called.
390 void OriginChipView::OnSafeBrowsingHit(
391 const SafeBrowsingUIManager::UnsafeResource& resource) {}
393 void OriginChipView::OnSafeBrowsingMatch(
394 const SafeBrowsingUIManager::UnsafeResource& resource) {