Home | History | Annotate | Download | only in omnibox
      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 // For WinDDK ATL compatibility, these ATL headers must come first.
      6 #include "build/build_config.h"
      7 #if defined(OS_WIN)
      8 #include <atlbase.h>  // NOLINT
      9 #include <atlwin.h>  // NOLINT
     10 #endif
     11 
     12 #include "chrome/browser/ui/views/omnibox/omnibox_result_view.h"
     13 
     14 #include <algorithm>  // NOLINT
     15 
     16 #include "base/i18n/bidi_line_iterator.h"
     17 #include "base/memory/scoped_vector.h"
     18 #include "base/strings/string_number_conversions.h"
     19 #include "base/strings/string_util.h"
     20 #include "chrome/browser/ui/omnibox/omnibox_popup_model.h"
     21 #include "chrome/browser/ui/views/location_bar/location_bar_view.h"
     22 #include "chrome/browser/ui/views/omnibox/omnibox_popup_contents_view.h"
     23 #include "grit/generated_resources.h"
     24 #include "grit/theme_resources.h"
     25 #include "ui/base/l10n/l10n_util.h"
     26 #include "ui/base/theme_provider.h"
     27 #include "ui/gfx/canvas.h"
     28 #include "ui/gfx/color_utils.h"
     29 #include "ui/gfx/image/image.h"
     30 #include "ui/gfx/range/range.h"
     31 #include "ui/gfx/render_text.h"
     32 #include "ui/gfx/text_elider.h"
     33 #include "ui/gfx/text_utils.h"
     34 #include "ui/native_theme/native_theme.h"
     35 
     36 using ui::NativeTheme;
     37 
     38 namespace {
     39 
     40 // The minimum distance between the top and bottom of the {icon|text} and the
     41 // top or bottom of the row.
     42 const int kMinimumIconVerticalPadding = 2;
     43 const int kMinimumTextVerticalPadding = 3;
     44 
     45 // A mapping from OmniboxResultView's ResultViewState/ColorKind types to
     46 // NativeTheme colors.
     47 struct TranslationTable {
     48   ui::NativeTheme::ColorId id;
     49   OmniboxResultView::ResultViewState state;
     50   OmniboxResultView::ColorKind kind;
     51 } static const kTranslationTable[] = {
     52   { NativeTheme::kColorId_ResultsTableNormalBackground,
     53     OmniboxResultView::NORMAL, OmniboxResultView::BACKGROUND },
     54   { NativeTheme::kColorId_ResultsTableHoveredBackground,
     55     OmniboxResultView::HOVERED, OmniboxResultView::BACKGROUND },
     56   { NativeTheme::kColorId_ResultsTableSelectedBackground,
     57     OmniboxResultView::SELECTED, OmniboxResultView::BACKGROUND },
     58   { NativeTheme::kColorId_ResultsTableNormalText,
     59     OmniboxResultView::NORMAL, OmniboxResultView::TEXT },
     60   { NativeTheme::kColorId_ResultsTableHoveredText,
     61     OmniboxResultView::HOVERED, OmniboxResultView::TEXT },
     62   { NativeTheme::kColorId_ResultsTableSelectedText,
     63     OmniboxResultView::SELECTED, OmniboxResultView::TEXT },
     64   { NativeTheme::kColorId_ResultsTableNormalDimmedText,
     65     OmniboxResultView::NORMAL, OmniboxResultView::DIMMED_TEXT },
     66   { NativeTheme::kColorId_ResultsTableHoveredDimmedText,
     67     OmniboxResultView::HOVERED, OmniboxResultView::DIMMED_TEXT },
     68   { NativeTheme::kColorId_ResultsTableSelectedDimmedText,
     69     OmniboxResultView::SELECTED, OmniboxResultView::DIMMED_TEXT },
     70   { NativeTheme::kColorId_ResultsTableNormalUrl,
     71     OmniboxResultView::NORMAL, OmniboxResultView::URL },
     72   { NativeTheme::kColorId_ResultsTableHoveredUrl,
     73     OmniboxResultView::HOVERED, OmniboxResultView::URL },
     74   { NativeTheme::kColorId_ResultsTableSelectedUrl,
     75     OmniboxResultView::SELECTED, OmniboxResultView::URL },
     76   { NativeTheme::kColorId_ResultsTableNormalDivider,
     77     OmniboxResultView::NORMAL, OmniboxResultView::DIVIDER },
     78   { NativeTheme::kColorId_ResultsTableHoveredDivider,
     79     OmniboxResultView::HOVERED, OmniboxResultView::DIVIDER },
     80   { NativeTheme::kColorId_ResultsTableSelectedDivider,
     81     OmniboxResultView::SELECTED, OmniboxResultView::DIVIDER },
     82 };
     83 
     84 }  // namespace
     85 
     86 ////////////////////////////////////////////////////////////////////////////////
     87 // OmniboxResultView, public:
     88 
     89 // This class is a utility class for calculations affected by whether the result
     90 // view is horizontally mirrored.  The drawing functions can be written as if
     91 // all drawing occurs left-to-right, and then use this class to get the actual
     92 // coordinates to begin drawing onscreen.
     93 class OmniboxResultView::MirroringContext {
     94  public:
     95   MirroringContext() : center_(0), right_(0) {}
     96 
     97   // Tells the mirroring context to use the provided range as the physical
     98   // bounds of the drawing region.  When coordinate mirroring is needed, the
     99   // mirror point will be the center of this range.
    100   void Initialize(int x, int width) {
    101     center_ = x + width / 2;
    102     right_ = x + width;
    103   }
    104 
    105   // Given a logical range within the drawing region, returns the coordinate of
    106   // the possibly-mirrored "left" side.  (This functions exactly like
    107   // View::MirroredLeftPointForRect().)
    108   int mirrored_left_coord(int left, int right) const {
    109     return base::i18n::IsRTL() ? (center_ + (center_ - right)) : left;
    110   }
    111 
    112   // Given a logical coordinate within the drawing region, returns the remaining
    113   // width available.
    114   int remaining_width(int x) const {
    115     return right_ - x;
    116   }
    117 
    118  private:
    119   int center_;
    120   int right_;
    121 
    122   DISALLOW_COPY_AND_ASSIGN(MirroringContext);
    123 };
    124 
    125 OmniboxResultView::OmniboxResultView(OmniboxPopupContentsView* model,
    126                                      int model_index,
    127                                      LocationBarView* location_bar_view,
    128                                      const gfx::FontList& font_list)
    129     : edge_item_padding_(LocationBarView::kItemPadding),
    130       item_padding_(LocationBarView::kItemPadding),
    131       minimum_text_vertical_padding_(kMinimumTextVerticalPadding),
    132       model_(model),
    133       model_index_(model_index),
    134       location_bar_view_(location_bar_view),
    135       font_list_(font_list),
    136       font_height_(
    137           std::max(font_list.GetHeight(),
    138                    font_list.DeriveWithStyle(gfx::Font::BOLD).GetHeight())),
    139       mirroring_context_(new MirroringContext()),
    140       keyword_icon_(new views::ImageView()),
    141       animation_(new gfx::SlideAnimation(this)) {
    142   CHECK_GE(model_index, 0);
    143   if (default_icon_size_ == 0) {
    144     default_icon_size_ =
    145         location_bar_view_->GetThemeProvider()->GetImageSkiaNamed(
    146             AutocompleteMatch::TypeToIcon(
    147                 AutocompleteMatchType::URL_WHAT_YOU_TYPED))->width();
    148   }
    149   keyword_icon_->set_owned_by_client();
    150   keyword_icon_->EnableCanvasFlippingForRTLUI(true);
    151   keyword_icon_->SetImage(GetKeywordIcon());
    152   keyword_icon_->SizeToPreferredSize();
    153 }
    154 
    155 OmniboxResultView::~OmniboxResultView() {
    156 }
    157 
    158 SkColor OmniboxResultView::GetColor(
    159     ResultViewState state,
    160     ColorKind kind) const {
    161   for (size_t i = 0; i < arraysize(kTranslationTable); ++i) {
    162     if (kTranslationTable[i].state == state &&
    163         kTranslationTable[i].kind == kind) {
    164       return GetNativeTheme()->GetSystemColor(kTranslationTable[i].id);
    165     }
    166   }
    167 
    168   NOTREACHED();
    169   return SK_ColorRED;
    170 }
    171 
    172 void OmniboxResultView::SetMatch(const AutocompleteMatch& match) {
    173   match_ = match;
    174   ResetRenderTexts();
    175   animation_->Reset();
    176 
    177   AutocompleteMatch* associated_keyword_match = match_.associated_keyword.get();
    178   if (associated_keyword_match) {
    179     keyword_icon_->SetImage(GetKeywordIcon());
    180     if (!keyword_icon_->parent())
    181       AddChildView(keyword_icon_.get());
    182   } else if (keyword_icon_->parent()) {
    183     RemoveChildView(keyword_icon_.get());
    184   }
    185 
    186   Layout();
    187 }
    188 
    189 void OmniboxResultView::ShowKeyword(bool show_keyword) {
    190   if (show_keyword)
    191     animation_->Show();
    192   else
    193     animation_->Hide();
    194 }
    195 
    196 void OmniboxResultView::Invalidate() {
    197   keyword_icon_->SetImage(GetKeywordIcon());
    198   // While the text in the RenderTexts may not have changed, the styling
    199   // (color/bold) may need to change. So we reset them to cause them to be
    200   // recomputed in OnPaint().
    201   ResetRenderTexts();
    202   SchedulePaint();
    203 }
    204 
    205 gfx::Size OmniboxResultView::GetPreferredSize() const {
    206   return gfx::Size(0, std::max(
    207       default_icon_size_ + (kMinimumIconVerticalPadding * 2),
    208       GetTextHeight() + (minimum_text_vertical_padding_ * 2)));
    209 }
    210 
    211 ////////////////////////////////////////////////////////////////////////////////
    212 // OmniboxResultView, protected:
    213 
    214 OmniboxResultView::ResultViewState OmniboxResultView::GetState() const {
    215   if (model_->IsSelectedIndex(model_index_))
    216     return SELECTED;
    217   return model_->IsHoveredIndex(model_index_) ? HOVERED : NORMAL;
    218 }
    219 
    220 int OmniboxResultView::GetTextHeight() const {
    221   return font_height_;
    222 }
    223 
    224 void OmniboxResultView::PaintMatch(
    225     const AutocompleteMatch& match,
    226     gfx::RenderText* contents,
    227     gfx::RenderText* description,
    228     gfx::Canvas* canvas,
    229     int x) const {
    230   int y = text_bounds_.y();
    231 
    232   if (!separator_rendertext_) {
    233     const base::string16& separator =
    234         l10n_util::GetStringUTF16(IDS_AUTOCOMPLETE_MATCH_DESCRIPTION_SEPARATOR);
    235     separator_rendertext_.reset(CreateRenderText(separator).release());
    236     separator_rendertext_->SetColor(GetColor(GetState(), DIMMED_TEXT));
    237     separator_width_ = separator_rendertext_->GetContentWidth();
    238   }
    239 
    240   int contents_max_width, description_max_width;
    241   OmniboxPopupModel::ComputeMatchMaxWidths(
    242       contents->GetContentWidth(),
    243       separator_width_,
    244       description ? description->GetContentWidth() : 0,
    245       mirroring_context_->remaining_width(x),
    246       !AutocompleteMatch::IsSearchType(match.type),
    247       &contents_max_width,
    248       &description_max_width);
    249 
    250   x = DrawRenderText(match, contents, true, canvas, x, y, contents_max_width);
    251 
    252   if (description_max_width != 0) {
    253     x = DrawRenderText(match, separator_rendertext_.get(), false, canvas, x, y,
    254                        separator_width_);
    255     DrawRenderText(match, description, false, canvas, x, y,
    256                    description_max_width);
    257   }
    258 }
    259 
    260 int OmniboxResultView::DrawRenderText(
    261     const AutocompleteMatch& match,
    262     gfx::RenderText* render_text,
    263     bool contents,
    264     gfx::Canvas* canvas,
    265     int x,
    266     int y,
    267     int max_width) const {
    268   DCHECK(!render_text->text().empty());
    269 
    270   const int remaining_width = mirroring_context_->remaining_width(x);
    271   int right_x = x + max_width;
    272 
    273   // Infinite suggestions should appear with the leading ellipses vertically
    274   // stacked.
    275   if (contents &&
    276       (match.type == AutocompleteMatchType::SEARCH_SUGGEST_INFINITE)) {
    277     // When the directionality of suggestion doesn't match the UI, we try to
    278     // vertically stack the ellipsis by restricting the end edge (right_x).
    279     const bool is_ui_rtl = base::i18n::IsRTL();
    280     const bool is_match_contents_rtl =
    281         (render_text->GetTextDirection() == base::i18n::RIGHT_TO_LEFT);
    282     const int offset =
    283         GetDisplayOffset(match, is_ui_rtl, is_match_contents_rtl);
    284 
    285     scoped_ptr<gfx::RenderText> prefix_render_text(
    286         CreateRenderText(base::UTF8ToUTF16(
    287             match.GetAdditionalInfo(kACMatchPropertyContentsPrefix))));
    288     const int prefix_width = prefix_render_text->GetContentWidth();
    289     int prefix_x = x;
    290 
    291     const int max_match_contents_width = model_->max_match_contents_width();
    292 
    293     if (is_ui_rtl != is_match_contents_rtl) {
    294       // RTL infinite suggestions appear near the left edge in LTR UI, while LTR
    295       // infinite suggestions appear near the right edge in RTL UI. This is
    296       // against the natural horizontal alignment of the text. We reduce the
    297       // width of the box for suggestion display, so that the suggestions appear
    298       // in correct confines.  This reduced width allows us to modify the text
    299       // alignment (see below).
    300       right_x = x + std::min(remaining_width - prefix_width,
    301                              std::max(offset, max_match_contents_width));
    302       prefix_x = right_x;
    303       // We explicitly set the horizontal alignment so that when LTR suggestions
    304       // show in RTL UI (or vice versa), their ellipses appear stacked in a
    305       // single column.
    306       render_text->SetHorizontalAlignment(
    307           is_match_contents_rtl ? gfx::ALIGN_RIGHT : gfx::ALIGN_LEFT);
    308     } else {
    309       // If the dropdown is wide enough, place the ellipsis at the position
    310       // where the omitted text would have ended. Otherwise reduce the offset of
    311       // the ellipsis such that the widest suggestion reaches the end of the
    312       // dropdown.
    313       const int start_offset = std::max(prefix_width,
    314           std::min(remaining_width - max_match_contents_width, offset));
    315       right_x = x + std::min(remaining_width, start_offset + max_width);
    316       x += start_offset;
    317       prefix_x = x - prefix_width;
    318     }
    319     prefix_render_text->SetDirectionalityMode(is_match_contents_rtl ?
    320         gfx::DIRECTIONALITY_FORCE_RTL : gfx::DIRECTIONALITY_FORCE_LTR);
    321     prefix_render_text->SetHorizontalAlignment(
    322           is_match_contents_rtl ? gfx::ALIGN_RIGHT : gfx::ALIGN_LEFT);
    323     prefix_render_text->SetDisplayRect(gfx::Rect(
    324           mirroring_context_->mirrored_left_coord(
    325               prefix_x, prefix_x + prefix_width), y,
    326           prefix_width, height()));
    327     prefix_render_text->Draw(canvas);
    328   }
    329 
    330   // Set the display rect to trigger eliding.
    331   render_text->SetDisplayRect(gfx::Rect(
    332       mirroring_context_->mirrored_left_coord(x, right_x), y,
    333       right_x - x, height()));
    334   render_text->Draw(canvas);
    335   return right_x;
    336 }
    337 
    338 scoped_ptr<gfx::RenderText> OmniboxResultView::CreateRenderText(
    339     const base::string16& text) const {
    340   scoped_ptr<gfx::RenderText> render_text(gfx::RenderText::CreateInstance());
    341   render_text->SetCursorEnabled(false);
    342   render_text->SetElideBehavior(gfx::ELIDE_TAIL);
    343   render_text->SetFontList(font_list_);
    344   render_text->SetText(text);
    345   return render_text.Pass();
    346 }
    347 
    348 scoped_ptr<gfx::RenderText> OmniboxResultView::CreateClassifiedRenderText(
    349     const base::string16& text,
    350     const ACMatchClassifications& classifications,
    351     bool force_dim) const {
    352   scoped_ptr<gfx::RenderText> render_text(CreateRenderText(text));
    353   const size_t text_length = render_text->text().length();
    354   for (size_t i = 0; i < classifications.size(); ++i) {
    355     const size_t text_start = classifications[i].offset;
    356     if (text_start >= text_length)
    357       break;
    358 
    359     const size_t text_end = (i < (classifications.size() - 1)) ?
    360         std::min(classifications[i + 1].offset, text_length) :
    361         text_length;
    362     const gfx::Range current_range(text_start, text_end);
    363 
    364     // Calculate style-related data.
    365     if (classifications[i].style & ACMatchClassification::MATCH)
    366       render_text->ApplyStyle(gfx::BOLD, true, current_range);
    367 
    368     ColorKind color_kind = TEXT;
    369     if (classifications[i].style & ACMatchClassification::URL) {
    370       color_kind = URL;
    371       // Consider logical string for domain "ABC.com/hello" where ABC are
    372       // Hebrew (RTL) characters. This string should ideally show as
    373       // "CBA.com/hello". If we do not force LTR on URL, it will appear as
    374       // "com/hello.CBA".
    375       // With IDN and RTL TLDs, it might be okay to allow RTL rendering of URLs,
    376       // but it still has some pitfalls like :
    377       // ABC.COM/abc-pqr/xyz/FGH will appear as HGF/abc-pqr/xyz/MOC.CBA which
    378       // really confuses the path hierarchy of the URL.
    379       // Also, if the URL supports https, the appearance will change into LTR
    380       // directionality.
    381       // In conclusion, LTR rendering of URL is probably the safest bet.
    382       render_text->SetDirectionalityMode(gfx::DIRECTIONALITY_FORCE_LTR);
    383     } else if (force_dim ||
    384         (classifications[i].style & ACMatchClassification::DIM)) {
    385       color_kind = DIMMED_TEXT;
    386     }
    387     render_text->ApplyColor(GetColor(GetState(), color_kind), current_range);
    388   }
    389   return render_text.Pass();
    390 }
    391 
    392 int OmniboxResultView::GetMatchContentsWidth() const {
    393   InitContentsRenderTextIfNecessary();
    394   return contents_rendertext_ ? contents_rendertext_->GetContentWidth() : 0;
    395 }
    396 
    397 // TODO(skanuj): This is probably identical across all OmniboxResultView rows in
    398 // the omnibox dropdown. Consider sharing the result.
    399 int OmniboxResultView::GetDisplayOffset(
    400     const AutocompleteMatch& match,
    401     bool is_ui_rtl,
    402     bool is_match_contents_rtl) const {
    403   if (match.type != AutocompleteMatchType::SEARCH_SUGGEST_INFINITE)
    404     return 0;
    405 
    406   const base::string16& input_text =
    407       base::UTF8ToUTF16(match.GetAdditionalInfo(kACMatchPropertyInputText));
    408   int contents_start_index = 0;
    409   base::StringToInt(match.GetAdditionalInfo(kACMatchPropertyContentsStartIndex),
    410                     &contents_start_index);
    411 
    412   scoped_ptr<gfx::RenderText> input_render_text(CreateRenderText(input_text));
    413   const gfx::Range& glyph_bounds =
    414       input_render_text->GetGlyphBounds(contents_start_index);
    415   const int start_padding = is_match_contents_rtl ?
    416       std::max(glyph_bounds.start(), glyph_bounds.end()) :
    417       std::min(glyph_bounds.start(), glyph_bounds.end());
    418 
    419   return is_ui_rtl ?
    420       (input_render_text->GetContentWidth() - start_padding) : start_padding;
    421 }
    422 
    423 // static
    424 int OmniboxResultView::default_icon_size_ = 0;
    425 
    426 gfx::ImageSkia OmniboxResultView::GetIcon() const {
    427   const gfx::Image image = model_->GetIconIfExtensionMatch(model_index_);
    428   if (!image.IsEmpty())
    429     return image.AsImageSkia();
    430 
    431   int icon = match_.starred ?
    432       IDR_OMNIBOX_STAR : AutocompleteMatch::TypeToIcon(match_.type);
    433   if (GetState() == SELECTED) {
    434     switch (icon) {
    435       case IDR_OMNIBOX_EXTENSION_APP:
    436         icon = IDR_OMNIBOX_EXTENSION_APP_SELECTED;
    437         break;
    438       case IDR_OMNIBOX_HTTP:
    439         icon = IDR_OMNIBOX_HTTP_SELECTED;
    440         break;
    441       case IDR_OMNIBOX_SEARCH:
    442         icon = IDR_OMNIBOX_SEARCH_SELECTED;
    443         break;
    444       case IDR_OMNIBOX_STAR:
    445         icon = IDR_OMNIBOX_STAR_SELECTED;
    446         break;
    447       default:
    448         NOTREACHED();
    449         break;
    450     }
    451   }
    452   return *(location_bar_view_->GetThemeProvider()->GetImageSkiaNamed(icon));
    453 }
    454 
    455 const gfx::ImageSkia* OmniboxResultView::GetKeywordIcon() const {
    456   // NOTE: If we ever begin returning icons of varying size, then callers need
    457   // to ensure that |keyword_icon_| is resized each time its image is reset.
    458   return location_bar_view_->GetThemeProvider()->GetImageSkiaNamed(
    459       (GetState() == SELECTED) ? IDR_OMNIBOX_TTS_SELECTED : IDR_OMNIBOX_TTS);
    460 }
    461 
    462 bool OmniboxResultView::ShowOnlyKeywordMatch() const {
    463   return match_.associated_keyword &&
    464       (keyword_icon_->x() <= icon_bounds_.right());
    465 }
    466 
    467 void OmniboxResultView::ResetRenderTexts() const {
    468   contents_rendertext_.reset();
    469   description_rendertext_.reset();
    470   separator_rendertext_.reset();
    471   keyword_contents_rendertext_.reset();
    472   keyword_description_rendertext_.reset();
    473 }
    474 
    475 void OmniboxResultView::InitContentsRenderTextIfNecessary() const {
    476   if (!contents_rendertext_) {
    477     contents_rendertext_.reset(
    478         CreateClassifiedRenderText(
    479             match_.contents, match_.contents_class, false).release());
    480   }
    481 }
    482 
    483 void OmniboxResultView::Layout() {
    484   const gfx::ImageSkia icon = GetIcon();
    485 
    486   icon_bounds_.SetRect(edge_item_padding_ +
    487       ((icon.width() == default_icon_size_) ?
    488           0 : LocationBarView::kIconInternalPadding),
    489       (height() - icon.height()) / 2, icon.width(), icon.height());
    490 
    491   int text_x = edge_item_padding_ + default_icon_size_ + item_padding_;
    492   int text_width = width() - text_x - edge_item_padding_;
    493 
    494   if (match_.associated_keyword.get()) {
    495     const int kw_collapsed_size =
    496         keyword_icon_->width() + edge_item_padding_;
    497     const int max_kw_x = width() - kw_collapsed_size;
    498     const int kw_x =
    499         animation_->CurrentValueBetween(max_kw_x, edge_item_padding_);
    500     const int kw_text_x = kw_x + keyword_icon_->width() + item_padding_;
    501 
    502     text_width = kw_x - text_x - item_padding_;
    503     keyword_text_bounds_.SetRect(
    504         kw_text_x, 0,
    505         std::max(width() - kw_text_x - edge_item_padding_, 0), height());
    506     keyword_icon_->SetPosition(
    507         gfx::Point(kw_x, (height() - keyword_icon_->height()) / 2));
    508   }
    509 
    510   text_bounds_.SetRect(text_x, 0, std::max(text_width, 0), height());
    511 }
    512 
    513 void OmniboxResultView::OnBoundsChanged(const gfx::Rect& previous_bounds) {
    514   animation_->SetSlideDuration(width() / 4);
    515 }
    516 
    517 void OmniboxResultView::OnPaint(gfx::Canvas* canvas) {
    518   const ResultViewState state = GetState();
    519   if (state != NORMAL)
    520     canvas->DrawColor(GetColor(state, BACKGROUND));
    521 
    522   // NOTE: While animating the keyword match, both matches may be visible.
    523 
    524   if (!ShowOnlyKeywordMatch()) {
    525     canvas->DrawImageInt(GetIcon(), GetMirroredXForRect(icon_bounds_),
    526                          icon_bounds_.y());
    527     int x = GetMirroredXForRect(text_bounds_);
    528     mirroring_context_->Initialize(x, text_bounds_.width());
    529     InitContentsRenderTextIfNecessary();
    530     if (!description_rendertext_ && !match_.description.empty()) {
    531       description_rendertext_.reset(
    532           CreateClassifiedRenderText(
    533               match_.description, match_.description_class, true).release());
    534     }
    535     PaintMatch(match_, contents_rendertext_.get(),
    536                description_rendertext_.get(), canvas, x);
    537   }
    538 
    539   AutocompleteMatch* keyword_match = match_.associated_keyword.get();
    540   if (keyword_match) {
    541     int x = GetMirroredXForRect(keyword_text_bounds_);
    542     mirroring_context_->Initialize(x, keyword_text_bounds_.width());
    543     if (!keyword_contents_rendertext_) {
    544       keyword_contents_rendertext_.reset(
    545           CreateClassifiedRenderText(keyword_match->contents,
    546                                      keyword_match->contents_class,
    547                                      false).release());
    548     }
    549     if (!keyword_description_rendertext_ &&
    550         !keyword_match->description.empty()) {
    551       keyword_description_rendertext_.reset(
    552           CreateClassifiedRenderText(keyword_match->description,
    553                                      keyword_match->description_class,
    554                                      true).release());
    555     }
    556     PaintMatch(*keyword_match, keyword_contents_rendertext_.get(),
    557                keyword_description_rendertext_.get(), canvas, x);
    558   }
    559 }
    560 
    561 void OmniboxResultView::AnimationProgressed(const gfx::Animation* animation) {
    562   Layout();
    563   SchedulePaint();
    564 }
    565