origin_chip_view.cc revision 46d4c2bc3267f3f028f39e7e311b0f89aba2e4fd
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. 4 5#include "chrome/browser/ui/views/location_bar/origin_chip_view.h" 6 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/canvas.h" 40#include "ui/gfx/font_list.h" 41#include "ui/gfx/text_utils.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" 46 47 48// OriginChipExtensionIcon ---------------------------------------------------- 49 50class OriginChipExtensionIcon : public extensions::IconImage::Observer { 51 public: 52 OriginChipExtensionIcon(LocationIconView* icon_view, 53 Profile* profile, 54 const extensions::Extension* extension); 55 virtual ~OriginChipExtensionIcon(); 56 57 // IconImage::Observer: 58 virtual void OnExtensionIconImageChanged( 59 extensions::IconImage* image) OVERRIDE; 60 61 private: 62 LocationIconView* icon_view_; 63 scoped_ptr<extensions::IconImage> icon_image_; 64 65 DISALLOW_COPY_AND_ASSIGN(OriginChipExtensionIcon); 66}; 67 68OriginChipExtensionIcon::OriginChipExtensionIcon( 69 LocationIconView* icon_view, 70 Profile* profile, 71 const extensions::Extension* extension) 72 : icon_view_(icon_view), 73 icon_image_(new extensions::IconImage( 74 profile, 75 extension, 76 extensions::IconsInfo::GetIcons(extension), 77 extension_misc::EXTENSION_ICON_BITTY, 78 extensions::util::GetDefaultAppIcon(), 79 this)) { 80 // Forces load of the image. 81 icon_image_->image_skia().GetRepresentation(1.0f); 82 83 if (!icon_image_->image_skia().image_reps().empty()) 84 OnExtensionIconImageChanged(icon_image_.get()); 85} 86 87OriginChipExtensionIcon::~OriginChipExtensionIcon() { 88} 89 90void OriginChipExtensionIcon::OnExtensionIconImageChanged( 91 extensions::IconImage* image) { 92 if (icon_view_) 93 icon_view_->SetImage(&icon_image_->image_skia()); 94} 95 96 97// OriginChipView ------------------------------------------------------------- 98 99namespace { 100 101const int kEdgeThickness = 5; 102const int k16x16IconLeadingSpacing = 1; 103const int k16x16IconTrailingSpacing = 2; 104const int kIconTextSpacing = 3; 105 106const int kNormalImages[3][9] = { 107 IMAGE_GRID(IDR_ORIGIN_CHIP_NORMAL), 108 IMAGE_GRID(IDR_ORIGIN_CHIP_HOVER), 109 IMAGE_GRID(IDR_ORIGIN_CHIP_PRESSED) 110}; 111 112const int kMalwareImages[3][9] = { 113 IMAGE_GRID(IDR_ORIGIN_CHIP_MALWARE_NORMAL), 114 IMAGE_GRID(IDR_ORIGIN_CHIP_MALWARE_HOVER), 115 IMAGE_GRID(IDR_ORIGIN_CHIP_MALWARE_PRESSED) 116}; 117 118const int kBrokenSSLImages[3][9] = { 119 IMAGE_GRID(IDR_ORIGIN_CHIP_BROKENSSL_NORMAL), 120 IMAGE_GRID(IDR_ORIGIN_CHIP_BROKENSSL_HOVER), 121 IMAGE_GRID(IDR_ORIGIN_CHIP_BROKENSSL_PRESSED) 122}; 123 124const int kEVImages[3][9] = { 125 IMAGE_GRID(IDR_ORIGIN_CHIP_EV_NORMAL), 126 IMAGE_GRID(IDR_ORIGIN_CHIP_EV_HOVER), 127 IMAGE_GRID(IDR_ORIGIN_CHIP_EV_PRESSED) 128}; 129 130const extensions::Extension* GetExtension(const GURL& url, Profile* profile) { 131 if (!url.SchemeIs(extensions::kExtensionScheme)) 132 return NULL; 133 ExtensionService* service = 134 extensions::ExtensionSystem::Get(profile)->extension_service(); 135 return service->extensions()->GetExtensionOrAppByURL(url); 136} 137 138} // namespace 139 140OriginChipView::OriginChipView(LocationBarView* location_bar_view, 141 Profile* profile, 142 const gfx::FontList& font_list) 143 : LabelButton(this, base::string16()), 144 location_bar_view_(location_bar_view), 145 profile_(profile), 146 showing_16x16_icon_(false), 147 fade_in_animation_(this) { 148 scoped_refptr<SafeBrowsingService> sb_service = 149 g_browser_process->safe_browsing_service(); 150 // May not be set for unit tests. 151 if (sb_service && sb_service->ui_manager()) 152 sb_service->ui_manager()->AddObserver(this); 153 154 SetFontList(font_list); 155 156 image()->EnableCanvasFlippingForRTLUI(false); 157 158 // TODO(gbillock): Would be nice to just use stock LabelButton stuff here. 159 location_icon_view_ = new LocationIconView(location_bar_view_); 160 // Make location icon hover events count as hovering the origin chip. 161 location_icon_view_->set_interactive(false); 162 location_icon_view_->ShowTooltip(true); 163 AddChildView(location_icon_view_); 164 165 ev_label_ = new views::Label(base::string16(), GetFontList()); 166 ev_label_->SetHorizontalAlignment(gfx::ALIGN_LEFT); 167 ev_label_->SetElideBehavior(gfx::TRUNCATE); 168 AddChildView(ev_label_); 169 170 host_label_ = new views::Label(base::string16(), GetFontList()); 171 host_label_->SetHorizontalAlignment(gfx::ALIGN_LEFT); 172 host_label_->SetElideBehavior(gfx::TRUNCATE); 173 AddChildView(host_label_); 174 175 fade_in_animation_.SetTweenType(gfx::Tween::LINEAR_OUT_SLOW_IN); 176 fade_in_animation_.SetSlideDuration(175); 177} 178 179OriginChipView::~OriginChipView() { 180 scoped_refptr<SafeBrowsingService> sb_service = 181 g_browser_process->safe_browsing_service(); 182 if (sb_service.get() && sb_service->ui_manager()) 183 sb_service->ui_manager()->RemoveObserver(this); 184} 185 186void OriginChipView::OnChanged() { 187 content::WebContents* web_contents = location_bar_view_->GetWebContents(); 188 if (!web_contents) 189 return; 190 191 // Note: security level can change async as the connection is made. 192 GURL url = location_bar_view_->GetToolbarModel()->GetURL(); 193 const ToolbarModel::SecurityLevel security_level = 194 location_bar_view_->GetToolbarModel()->GetSecurityLevel(true); 195 196 bool url_malware = OriginChip::IsMalware(url, web_contents); 197 198 // TODO(gbillock): We persist a malware setting while a new WebContents 199 // content is loaded, meaning that we end up transiently marking a safe 200 // page as malware. Need to fix that. 201 202 if ((url == url_displayed_) && 203 (security_level == security_level_) && 204 (url_malware == url_malware_)) 205 return; 206 207 url_displayed_ = url; 208 url_malware_ = url_malware; 209 security_level_ = security_level; 210 211 if (url_malware_) { 212 SetBorderImages(kMalwareImages); 213 } else if (security_level_ == ToolbarModel::SECURITY_ERROR) { 214 SetBorderImages(kBrokenSSLImages); 215 } else if (security_level_ == ToolbarModel::EV_SECURE) { 216 SetBorderImages(kEVImages); 217 } else { 218 SetBorderImages(kNormalImages); 219 } 220 221 ev_label_->SetText(location_bar_view_->GetToolbarModel()->GetEVCertName()); 222 ev_label_->SetVisible(security_level_ == ToolbarModel::EV_SECURE); 223 224 // TODO(pkasting): Allow the origin chip to shrink, and use ElideHost(). 225 base::string16 host = 226 OriginChip::LabelFromURLForProfile(url_displayed_, profile_); 227 host_label_->SetText(host); 228 host_label_->SetTooltipText(base::UTF8ToUTF16(url.spec())); 229 230 showing_16x16_icon_ = url_displayed_.is_empty() || 231 url_displayed_.SchemeIs(content::kChromeUIScheme); 232 int icon = showing_16x16_icon_ ? IDR_PRODUCT_LOGO_16 : 233 location_bar_view_->GetToolbarModel()->GetIconForSecurityLevel( 234 security_level_); 235 const extensions::Extension* extension = 236 GetExtension(url_displayed_, profile_); 237 if (extension) { 238 icon = IDR_EXTENSIONS_FAVICON; 239 showing_16x16_icon_ = true; 240 extension_icon_.reset( 241 new OriginChipExtensionIcon(location_icon_view_, profile_, extension)); 242 } else { 243 extension_icon_.reset(); 244 } 245 location_icon_view_->SetImage(GetThemeProvider()->GetImageSkiaNamed(icon)); 246 247 if (visible()) { 248 CancelFade(); 249 Layout(); 250 SchedulePaint(); 251 } 252} 253 254void OriginChipView::FadeIn() { 255 fade_in_animation_.Show(); 256} 257 258void OriginChipView::CancelFade() { 259 fade_in_animation_.Stop(); 260} 261 262int OriginChipView::HostLabelOffset() const { 263 return host_label_->x() - GetLabelX(); 264} 265 266int OriginChipView::WidthFromStartOfLabels() const { 267 return width() - GetLabelX(); 268} 269 270gfx::Size OriginChipView::GetPreferredSize() const { 271 // TODO(pkasting): Use of " " here is a horrible hack, to be replaced by 272 // splitting the chip into separate pieces for EV/host. 273 int label_size = host_label_->GetPreferredSize().width(); 274 if (ev_label_->visible()) { 275 label_size += ev_label_->GetPreferredSize().width() + 276 gfx::GetStringWidth(base::ASCIIToUTF16(" "), GetFontList()); 277 } 278 return gfx::Size(GetLabelX() + label_size + kEdgeThickness, 279 location_icon_view_->GetPreferredSize().height()); 280} 281 282void OriginChipView::Layout() { 283 // TODO(gbillock): Eventually we almost certainly want to use 284 // LocationBarLayout for leading and trailing decorations. 285 286 location_icon_view_->SetBounds( 287 kEdgeThickness + (showing_16x16_icon_ ? k16x16IconLeadingSpacing : 0), 288 LocationBarView::kNormalEdgeThickness, 289 location_icon_view_->GetPreferredSize().width(), 290 height() - 2 * LocationBarView::kNormalEdgeThickness); 291 292 int label_x = GetLabelX(); 293 int label_width = std::max(0, width() - label_x - kEdgeThickness); 294 const int label_y = LocationBarView::kNormalEdgeThickness; 295 const int label_height = height() - 2 * LocationBarView::kNormalEdgeThickness; 296 if (ev_label_->visible()) { 297 int ev_label_width = 298 std::min(ev_label_->GetPreferredSize().width(), label_width); 299 ev_label_->SetBounds(label_x, label_y, ev_label_width, label_height); 300 // TODO(pkasting): See comments in GetPreferredSize(). 301 ev_label_width += 302 gfx::GetStringWidth(base::ASCIIToUTF16(" "), GetFontList()); 303 label_x += ev_label_width; 304 label_width = std::max(0, label_width - ev_label_width); 305 } 306 host_label_->SetBounds(label_x, label_y, label_width, label_height); 307} 308 309int OriginChipView::GetLabelX() const { 310 const int icon_spacing = showing_16x16_icon_ ? 311 (k16x16IconLeadingSpacing + k16x16IconTrailingSpacing) : 0; 312 return kEdgeThickness + location_icon_view_->GetPreferredSize().width() + 313 icon_spacing + kIconTextSpacing; 314} 315 316void OriginChipView::SetBorderImages(const int images[3][9]) { 317 scoped_ptr<views::LabelButtonBorder> border( 318 new views::LabelButtonBorder(views::Button::STYLE_BUTTON)); 319 320 for (size_t i = 0; i < 3; ++i) { 321 views::Painter* painter = views::Painter::CreateImageGridPainter(images[i]); 322 border->SetPainter(false, static_cast<Button::ButtonState>(i), painter); 323 324 // Calculate a representative background color of the provided image grid to 325 // use as the background color of the host label in order to color the text 326 // appropriately. We grab the color of the middle pixel of the middle image 327 // of the background, which we treat as the representative color of the 328 // entire background (reasonable, given the current appearance of these 329 // images). 330 const SkBitmap& bitmap( 331 GetThemeProvider()->GetImageSkiaNamed( 332 images[i][4])->GetRepresentation(1.0f).sk_bitmap()); 333 SkAutoLockPixels pixel_lock(bitmap); 334 background_colors_[i] = 335 bitmap.getColor(bitmap.width() / 2, bitmap.height() / 2); 336 } 337 338 // Calculate the actual text color of the pressed label. 339 host_label_->SetBackgroundColor(background_colors_[Button::STATE_PRESSED]); 340 pressed_text_color_ = host_label_->enabled_color(); 341 host_label_->SetBackgroundColor(background_colors_[state()]); 342 343 SetBorder(border.PassAs<views::Border>()); 344} 345 346void OriginChipView::AnimationProgressed(const gfx::Animation* animation) { 347 if (animation == &fade_in_animation_) 348 SchedulePaint(); 349 else 350 views::LabelButton::AnimationProgressed(animation); 351} 352 353void OriginChipView::AnimationEnded(const gfx::Animation* animation) { 354 if (animation == &fade_in_animation_) 355 fade_in_animation_.Reset(); 356 else 357 views::LabelButton::AnimationEnded(animation); 358} 359 360void OriginChipView::OnPaintBorder(gfx::Canvas* canvas) { 361 if (fade_in_animation_.is_animating()) { 362 canvas->SaveLayerAlpha(static_cast<uint8>( 363 fade_in_animation_.CurrentValueBetween(0, 255))); 364 views::LabelButton::OnPaintBorder(canvas); 365 canvas->Restore(); 366 } else { 367 views::LabelButton::OnPaintBorder(canvas); 368 } 369} 370 371void OriginChipView::StateChanged() { 372 DCHECK_LT(state(), 3); 373 SkColor background_color = background_colors_[state()]; 374 ev_label_->SetBackgroundColor(background_color); 375 host_label_->SetBackgroundColor(background_color); 376} 377 378// TODO(gbillock): Make the LocationBarView or OmniboxView the listener for 379// this button. 380void OriginChipView::ButtonPressed(views::Button* sender, 381 const ui::Event& event) { 382 // See if the event needs to be passed to the LocationIconView. 383 if (event.IsMouseEvent() || (event.type() == ui::ET_GESTURE_TAP)) { 384 location_icon_view_->set_interactive(true); 385 const ui::LocatedEvent& located_event = 386 static_cast<const ui::LocatedEvent&>(event); 387 if (GetEventHandlerForPoint(located_event.location()) == 388 location_icon_view_) { 389 location_icon_view_->page_info_helper()->ProcessEvent(located_event); 390 location_icon_view_->set_interactive(false); 391 return; 392 } 393 location_icon_view_->set_interactive(false); 394 } 395 396 UMA_HISTOGRAM_COUNTS("OriginChip.Pressed", 1); 397 content::RecordAction(base::UserMetricsAction("OriginChipPress")); 398 399 location_bar_view_->ShowURL(); 400} 401 402// Note: When OnSafeBrowsingHit would be called, OnSafeBrowsingMatch will 403// have already been called. 404void OriginChipView::OnSafeBrowsingHit( 405 const SafeBrowsingUIManager::UnsafeResource& resource) {} 406 407void OriginChipView::OnSafeBrowsingMatch( 408 const SafeBrowsingUIManager::UnsafeResource& resource) { 409 OnChanged(); 410} 411