origin_chip_view.cc revision 03b57e008b61dfcb1fbad3aea950ae0e001748b0
177a5bcaff43df8d54e0e0ef833726e4b41d7eb36Eric Anholt// Copyright 2014 The Chromium Authors. All rights reserved.
277a5bcaff43df8d54e0e0ef833726e4b41d7eb36Eric Anholt// Use of this source code is governed by a BSD-style license that can be
3ecadb51bbcb972a79f3ed79e65a7986b9396e757Brian Paul// found in the LICENSE file.
47554b83a21bd62b20df5a7327b69f08108ac9ab6Eric Anholt
5645f220710923f72e80281fa75e577e133b5a53cBrian Paul#include "chrome/browser/ui/views/location_bar/origin_chip_view.h"
6645f220710923f72e80281fa75e577e133b5a53cBrian Paul
7e339b669a14f37698b842c0c51c1f5e4001ef12fEric Anholt#include "base/files/file_path.h"
8e339b669a14f37698b842c0c51c1f5e4001ef12fEric Anholt#include "base/metrics/histogram.h"
9e339b669a14f37698b842c0c51c1f5e4001ef12fEric Anholt#include "base/strings/string_util.h"
10e339b669a14f37698b842c0c51c1f5e4001ef12fEric Anholt#include "base/strings/utf_string_conversions.h"
11e339b669a14f37698b842c0c51c1f5e4001ef12fEric Anholt#include "chrome/browser/browser_process.h"
12e339b669a14f37698b842c0c51c1f5e4001ef12fEric Anholt#include "chrome/browser/extensions/extension_service.h"
13e339b669a14f37698b842c0c51c1f5e4001ef12fEric Anholt#include "chrome/browser/extensions/extension_util.h"
14e339b669a14f37698b842c0c51c1f5e4001ef12fEric Anholt#include "chrome/browser/favicon/favicon_tab_helper.h"
15e339b669a14f37698b842c0c51c1f5e4001ef12fEric Anholt#include "chrome/browser/profiles/profile.h"
16e339b669a14f37698b842c0c51c1f5e4001ef12fEric Anholt#include "chrome/browser/safe_browsing/safe_browsing_service.h"
170a3c895f341ae458b09dcdc9cbd674600366a925Brian Paul#include "chrome/browser/safe_browsing/ui_manager.h"
18e339b669a14f37698b842c0c51c1f5e4001ef12fEric Anholt#include "chrome/browser/search/search.h"
19dad95c112f09b9978d71ababe80c4e34121d49f0Eric Anholt#include "chrome/browser/themes/theme_properties.h"
20dad95c112f09b9978d71ababe80c4e34121d49f0Eric Anholt#include "chrome/browser/ui/browser.h"
21e339b669a14f37698b842c0c51c1f5e4001ef12fEric Anholt#include "chrome/browser/ui/elide_url.h"
22e339b669a14f37698b842c0c51c1f5e4001ef12fEric Anholt#include "chrome/browser/ui/location_bar/origin_chip_info.h"
23e339b669a14f37698b842c0c51c1f5e4001ef12fEric Anholt#include "chrome/browser/ui/omnibox/omnibox_view.h"
24e339b669a14f37698b842c0c51c1f5e4001ef12fEric Anholt#include "chrome/browser/ui/toolbar/toolbar_model.h"
25b5c847c7ca06823af3b72324056a2e478caca70bChad Versace#include "chrome/browser/ui/views/location_bar/location_bar_view.h"
26e339b669a14f37698b842c0c51c1f5e4001ef12fEric Anholt#include "chrome/common/extensions/extension_constants.h"
27e339b669a14f37698b842c0c51c1f5e4001ef12fEric Anholt#include "content/public/browser/user_metrics.h"
28e339b669a14f37698b842c0c51c1f5e4001ef12fEric Anholt#include "content/public/browser/web_contents.h"
29e339b669a14f37698b842c0c51c1f5e4001ef12fEric Anholt#include "content/public/common/url_constants.h"
30e339b669a14f37698b842c0c51c1f5e4001ef12fEric Anholt#include "extensions/browser/extension_icon_image.h"
31e339b669a14f37698b842c0c51c1f5e4001ef12fEric Anholt#include "extensions/browser/extension_system.h"
32e339b669a14f37698b842c0c51c1f5e4001ef12fEric Anholt#include "extensions/common/constants.h"
33e339b669a14f37698b842c0c51c1f5e4001ef12fEric Anholt#include "extensions/common/manifest_handlers/icons_handler.h"
34e339b669a14f37698b842c0c51c1f5e4001ef12fEric Anholt#include "grit/theme_resources.h"
3587478cd6e3e6349abc2688b390f8845d1e21af3dEric Anholt#include "ui/base/resource/resource_bundle.h"
3687478cd6e3e6349abc2688b390f8845d1e21af3dEric Anholt#include "ui/base/theme_provider.h"
3787478cd6e3e6349abc2688b390f8845d1e21af3dEric Anholt#include "ui/gfx/canvas.h"
3887478cd6e3e6349abc2688b390f8845d1e21af3dEric Anholt#include "ui/gfx/font_list.h"
3987478cd6e3e6349abc2688b390f8845d1e21af3dEric Anholt#include "ui/gfx/text_utils.h"
4087478cd6e3e6349abc2688b390f8845d1e21af3dEric Anholt#include "ui/views/background.h"
4187478cd6e3e6349abc2688b390f8845d1e21af3dEric Anholt#include "ui/views/controls/button/label_button.h"
4287478cd6e3e6349abc2688b390f8845d1e21af3dEric Anholt#include "ui/views/controls/button/label_button_border.h"
4387478cd6e3e6349abc2688b390f8845d1e21af3dEric Anholt#include "ui/views/painter.h"
44507e71e45a2e46c6e01423ab00948a72f435396cEric Anholt
45507e71e45a2e46c6e01423ab00948a72f435396cEric Anholt
46507e71e45a2e46c6e01423ab00948a72f435396cEric Anholt// OriginChipExtensionIcon ----------------------------------------------------
47507e71e45a2e46c6e01423ab00948a72f435396cEric Anholt
48e339b669a14f37698b842c0c51c1f5e4001ef12fEric Anholtclass OriginChipExtensionIcon : public extensions::IconImage::Observer {
49b5444a6ebd48a2bf4c258be98aac831636164e10Eric Anholt public:
50b5444a6ebd48a2bf4c258be98aac831636164e10Eric Anholt  OriginChipExtensionIcon(LocationIconView* icon_view,
51b5444a6ebd48a2bf4c258be98aac831636164e10Eric Anholt                          Profile* profile,
52b5444a6ebd48a2bf4c258be98aac831636164e10Eric Anholt                          const extensions::Extension* extension);
53e339b669a14f37698b842c0c51c1f5e4001ef12fEric Anholt  virtual ~OriginChipExtensionIcon();
54e339b669a14f37698b842c0c51c1f5e4001ef12fEric Anholt
55e339b669a14f37698b842c0c51c1f5e4001ef12fEric Anholt  // IconImage::Observer:
5677a5bcaff43df8d54e0e0ef833726e4b41d7eb36Eric Anholt  virtual void OnExtensionIconImageChanged(
5777a5bcaff43df8d54e0e0ef833726e4b41d7eb36Eric Anholt      extensions::IconImage* image) OVERRIDE;
587554b83a21bd62b20df5a7327b69f08108ac9ab6Eric Anholt
597554b83a21bd62b20df5a7327b69f08108ac9ab6Eric Anholt private:
607554b83a21bd62b20df5a7327b69f08108ac9ab6Eric Anholt  LocationIconView* icon_view_;
617554b83a21bd62b20df5a7327b69f08108ac9ab6Eric Anholt  scoped_ptr<extensions::IconImage> icon_image_;
627554b83a21bd62b20df5a7327b69f08108ac9ab6Eric Anholt
637554b83a21bd62b20df5a7327b69f08108ac9ab6Eric Anholt  DISALLOW_COPY_AND_ASSIGN(OriginChipExtensionIcon);
6418a6e5ee73c5cef283c6bef906e5f8e4f60d4000Eric Anholt};
6577a5bcaff43df8d54e0e0ef833726e4b41d7eb36Eric Anholt
66OriginChipExtensionIcon::OriginChipExtensionIcon(
67    LocationIconView* icon_view,
68    Profile* profile,
69    const extensions::Extension* extension)
70    : icon_view_(icon_view),
71      icon_image_(new extensions::IconImage(
72          profile,
73          extension,
74          extensions::IconsInfo::GetIcons(extension),
75          extension_misc::EXTENSION_ICON_BITTY,
76          extensions::util::GetDefaultAppIcon(),
77          this)) {
78  // Forces load of the image.
79  icon_image_->image_skia().GetRepresentation(1.0f);
80
81  if (!icon_image_->image_skia().image_reps().empty())
82    OnExtensionIconImageChanged(icon_image_.get());
83}
84
85OriginChipExtensionIcon::~OriginChipExtensionIcon() {
86}
87
88void OriginChipExtensionIcon::OnExtensionIconImageChanged(
89    extensions::IconImage* image) {
90  if (icon_view_)
91    icon_view_->SetImage(&icon_image_->image_skia());
92}
93
94
95// OriginChipView -------------------------------------------------------------
96
97namespace {
98
99const int kEdgeThickness = 5;
100const int k16x16IconLeadingSpacing = 1;
101const int k16x16IconTrailingSpacing = 2;
102const int kIconTextSpacing = 3;
103
104const int kNormalImages[3][9] = {
105  IMAGE_GRID(IDR_ORIGIN_CHIP_NORMAL),
106  IMAGE_GRID(IDR_ORIGIN_CHIP_HOVER),
107  IMAGE_GRID(IDR_ORIGIN_CHIP_PRESSED)
108};
109
110const int kMalwareImages[3][9] = {
111  IMAGE_GRID(IDR_ORIGIN_CHIP_MALWARE_NORMAL),
112  IMAGE_GRID(IDR_ORIGIN_CHIP_MALWARE_HOVER),
113  IMAGE_GRID(IDR_ORIGIN_CHIP_MALWARE_PRESSED)
114};
115
116const int kBrokenSSLImages[3][9] = {
117  IMAGE_GRID(IDR_ORIGIN_CHIP_BROKENSSL_NORMAL),
118  IMAGE_GRID(IDR_ORIGIN_CHIP_BROKENSSL_HOVER),
119  IMAGE_GRID(IDR_ORIGIN_CHIP_BROKENSSL_PRESSED)
120};
121
122const int kEVImages[3][9] = {
123  IMAGE_GRID(IDR_ORIGIN_CHIP_EV_NORMAL),
124  IMAGE_GRID(IDR_ORIGIN_CHIP_EV_HOVER),
125  IMAGE_GRID(IDR_ORIGIN_CHIP_EV_PRESSED)
126};
127
128const extensions::Extension* GetExtension(const GURL& url, Profile* profile) {
129  if (!url.SchemeIs(extensions::kExtensionScheme))
130    return NULL;
131  ExtensionService* service =
132      extensions::ExtensionSystem::Get(profile)->extension_service();
133  return service->extensions()->GetExtensionOrAppByURL(url);
134}
135
136}  // namespace
137
138OriginChipView::OriginChipView(LocationBarView* location_bar_view,
139                               Profile* profile,
140                               const gfx::FontList& font_list)
141    : LabelButton(this, base::string16()),
142      location_bar_view_(location_bar_view),
143      profile_(profile),
144      showing_16x16_icon_(false),
145      fade_in_animation_(this),
146      security_level_(ToolbarModel::NONE),
147      url_malware_(false) {
148  EnableCanvasFlippingForRTLUI(true);
149
150  scoped_refptr<SafeBrowsingService> sb_service =
151      g_browser_process->safe_browsing_service();
152  // |sb_service| may be NULL in tests.
153  if (sb_service && sb_service->ui_manager())
154    sb_service->ui_manager()->AddObserver(this);
155
156  SetFontList(font_list);
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::NO_ELIDE);
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::NO_ELIDE);
173  AddChildView(host_label_);
174
175  fade_in_animation_.SetTweenType(gfx::Tween::LINEAR_OUT_SLOW_IN);
176  fade_in_animation_.SetSlideDuration(175);
177
178  // Ensure |pressed_text_color_| and |background_colors_| are initialized.
179  SetBorderImages(kNormalImages);
180}
181
182OriginChipView::~OriginChipView() {
183  scoped_refptr<SafeBrowsingService> sb_service =
184      g_browser_process->safe_browsing_service();
185  if (sb_service.get() && sb_service->ui_manager())
186    sb_service->ui_manager()->RemoveObserver(this);
187}
188
189void OriginChipView::OnChanged() {
190  content::WebContents* web_contents = location_bar_view_->GetWebContents();
191  if (!web_contents)
192    return;
193
194  // Note: security level can change async as the connection is made.
195  GURL url = location_bar_view_->GetToolbarModel()->GetURL();
196  const ToolbarModel::SecurityLevel security_level =
197      location_bar_view_->GetToolbarModel()->GetSecurityLevel(true);
198
199  bool url_malware = OriginChip::IsMalware(url, web_contents);
200
201  // TODO(gbillock): We persist a malware setting while a new WebContents
202  // content is loaded, meaning that we end up transiently marking a safe
203  // page as malware. Need to fix that.
204
205  if ((url == url_displayed_) &&
206      (security_level == security_level_) &&
207      (url_malware == url_malware_))
208    return;
209
210  url_displayed_ = url;
211  url_malware_ = url_malware;
212  security_level_ = security_level;
213
214  if (url_malware_) {
215    SetBorderImages(kMalwareImages);
216  } else if (security_level_ == ToolbarModel::SECURITY_ERROR) {
217    SetBorderImages(kBrokenSSLImages);
218  } else if (security_level_ == ToolbarModel::EV_SECURE) {
219    SetBorderImages(kEVImages);
220  } else {
221    SetBorderImages(kNormalImages);
222  }
223
224  ev_label_->SetText(location_bar_view_->GetToolbarModel()->GetEVCertName());
225  ev_label_->SetVisible(security_level_ == ToolbarModel::EV_SECURE);
226
227  // TODO(pkasting): Allow the origin chip to shrink, and use ElideHost().
228  base::string16 host =
229      OriginChip::LabelFromURLForProfile(url_displayed_, profile_);
230  host_label_->SetText(host);
231  host_label_->SetTooltipText(base::UTF8ToUTF16(url.spec()));
232
233  showing_16x16_icon_ = url_displayed_.is_empty() ||
234      url_displayed_.SchemeIs(content::kChromeUIScheme);
235  int icon = showing_16x16_icon_ ? IDR_PRODUCT_LOGO_16 :
236      location_bar_view_->GetToolbarModel()->GetIconForSecurityLevel(
237          security_level_);
238  const extensions::Extension* extension =
239      GetExtension(url_displayed_, profile_);
240  if (extension) {
241    icon = IDR_EXTENSIONS_FAVICON;
242    showing_16x16_icon_ = true;
243    extension_icon_.reset(
244        new OriginChipExtensionIcon(location_icon_view_, profile_, extension));
245  } else {
246    extension_icon_.reset();
247  }
248  location_icon_view_->SetImage(GetThemeProvider()->GetImageSkiaNamed(icon));
249
250  if (visible()) {
251    CancelFade();
252    Layout();
253    SchedulePaint();
254  }
255}
256
257void OriginChipView::FadeIn() {
258  fade_in_animation_.Show();
259}
260
261void OriginChipView::CancelFade() {
262  fade_in_animation_.Stop();
263}
264
265int OriginChipView::HostLabelOffset() const {
266  return host_label_->x() - GetLabelX();
267}
268
269int OriginChipView::WidthFromStartOfLabels() const {
270  return width() - GetLabelX();
271}
272
273gfx::Size OriginChipView::GetPreferredSize() const {
274  // TODO(pkasting): Use of " " here is a horrible hack, to be replaced by
275  // splitting the chip into separate pieces for EV/host.
276  int label_size = host_label_->GetPreferredSize().width();
277  if (ev_label_->visible()) {
278    label_size += ev_label_->GetPreferredSize().width() +
279        gfx::GetStringWidth(base::ASCIIToUTF16(" "), GetFontList());
280  }
281  return gfx::Size(GetLabelX() + label_size + kEdgeThickness,
282                   location_icon_view_->GetPreferredSize().height());
283}
284
285void OriginChipView::Layout() {
286  // TODO(gbillock): Eventually we almost certainly want to use
287  // LocationBarLayout for leading and trailing decorations.
288
289  location_icon_view_->SetBounds(
290      kEdgeThickness + (showing_16x16_icon_ ? k16x16IconLeadingSpacing : 0),
291      LocationBarView::kNormalEdgeThickness,
292      location_icon_view_->GetPreferredSize().width(),
293      height() - 2 * LocationBarView::kNormalEdgeThickness);
294
295  int label_x = GetLabelX();
296  int label_width = std::max(0, width() - label_x - kEdgeThickness);
297  const int label_y = LocationBarView::kNormalEdgeThickness;
298  const int label_height = height() - 2 * LocationBarView::kNormalEdgeThickness;
299  if (ev_label_->visible()) {
300    int ev_label_width =
301        std::min(ev_label_->GetPreferredSize().width(), label_width);
302    ev_label_->SetBounds(label_x, label_y, ev_label_width, label_height);
303    // TODO(pkasting): See comments in GetPreferredSize().
304    ev_label_width +=
305        gfx::GetStringWidth(base::ASCIIToUTF16(" "), GetFontList());
306    label_x += ev_label_width;
307    label_width = std::max(0, label_width - ev_label_width);
308  }
309  host_label_->SetBounds(label_x, label_y, label_width, label_height);
310}
311
312int OriginChipView::GetLabelX() const {
313  const int icon_spacing = showing_16x16_icon_ ?
314      (k16x16IconLeadingSpacing + k16x16IconTrailingSpacing) : 0;
315  return kEdgeThickness + location_icon_view_->GetPreferredSize().width() +
316      icon_spacing + kIconTextSpacing;
317}
318
319void OriginChipView::SetBorderImages(const int images[3][9]) {
320  scoped_ptr<views::LabelButtonBorder> border(
321      new views::LabelButtonBorder(views::Button::STYLE_BUTTON));
322
323  for (size_t i = 0; i < 3; ++i) {
324    views::Painter* painter = views::Painter::CreateImageGridPainter(images[i]);
325    border->SetPainter(false, static_cast<Button::ButtonState>(i), painter);
326
327    // Calculate a representative background color of the provided image grid to
328    // use as the background color of the host label in order to color the text
329    // appropriately. We grab the color of the middle pixel of the middle image
330    // of the background, which we treat as the representative color of the
331    // entire background (reasonable, given the current appearance of these
332    // images).
333    //
334    // NOTE: Because this is called from the constructor, when we're not in a
335    // Widget yet, GetThemeProvider() may return NULL, so use the location bar's
336    // theme provider instead to be safe.
337    const SkBitmap& bitmap(
338        location_bar_view_->GetThemeProvider()->GetImageSkiaNamed(
339            images[i][4])->GetRepresentation(1.0f).sk_bitmap());
340    SkAutoLockPixels pixel_lock(bitmap);
341    background_colors_[i] =
342        bitmap.getColor(bitmap.width() / 2, bitmap.height() / 2);
343  }
344
345  // Calculate the actual text color of the pressed label.
346  host_label_->SetBackgroundColor(background_colors_[Button::STATE_PRESSED]);
347  pressed_text_color_ = host_label_->enabled_color();
348  host_label_->SetBackgroundColor(background_colors_[state()]);
349
350  SetBorder(border.PassAs<views::Border>());
351}
352
353void OriginChipView::AnimationProgressed(const gfx::Animation* animation) {
354  if (animation == &fade_in_animation_)
355    SchedulePaint();
356  else
357    views::LabelButton::AnimationProgressed(animation);
358}
359
360void OriginChipView::AnimationEnded(const gfx::Animation* animation) {
361  if (animation == &fade_in_animation_)
362    fade_in_animation_.Reset();
363  else
364    views::LabelButton::AnimationEnded(animation);
365}
366
367void OriginChipView::OnPaintBorder(gfx::Canvas* canvas) {
368  if (fade_in_animation_.is_animating()) {
369    canvas->SaveLayerAlpha(static_cast<uint8>(
370        fade_in_animation_.CurrentValueBetween(0, 255)));
371    views::LabelButton::OnPaintBorder(canvas);
372    canvas->Restore();
373  } else {
374    views::LabelButton::OnPaintBorder(canvas);
375  }
376}
377
378void OriginChipView::StateChanged() {
379  DCHECK_LT(state(), 3);
380  SkColor background_color = background_colors_[state()];
381  ev_label_->SetBackgroundColor(background_color);
382  host_label_->SetBackgroundColor(background_color);
383}
384
385// TODO(gbillock): Make the LocationBarView or OmniboxView the listener for
386// this button.
387void OriginChipView::ButtonPressed(views::Button* sender,
388                                 const ui::Event& event) {
389  // See if the event needs to be passed to the LocationIconView.
390  if (event.IsMouseEvent() || (event.type() == ui::ET_GESTURE_TAP)) {
391    location_icon_view_->set_interactive(true);
392    const ui::LocatedEvent& located_event =
393        static_cast<const ui::LocatedEvent&>(event);
394    if (GetEventHandlerForPoint(located_event.location()) ==
395            location_icon_view_) {
396      location_icon_view_->page_info_helper()->ProcessEvent(located_event);
397      location_icon_view_->set_interactive(false);
398      return;
399    }
400    location_icon_view_->set_interactive(false);
401  }
402
403  UMA_HISTOGRAM_COUNTS("OriginChip.Pressed", 1);
404  content::RecordAction(base::UserMetricsAction("OriginChipPress"));
405
406  location_bar_view_->ShowURL();
407}
408
409// Note: When OnSafeBrowsingHit would be called, OnSafeBrowsingMatch will
410// have already been called.
411void OriginChipView::OnSafeBrowsingHit(
412    const SafeBrowsingUIManager::UnsafeResource& resource) {}
413
414void OriginChipView::OnSafeBrowsingMatch(
415    const SafeBrowsingUIManager::UnsafeResource& resource) {
416  OnChanged();
417}
418