[iOS] Add SwitchToTab button to the omnibox popup

This CL adds the SwitchToTab image and handling to the omnibox popup
row. It allows to switch easily to an opened tab based on omnibox
suggestions.

Bug: 893121
Cq-Include-Trybots: luci.chromium.try:ios-simulator-cronet;luci.chromium.try:ios-simulator-full-configs
Change-Id: Ib440267802c1210ea416066c679a2ff4e963fbb6
Reviewed-on: https://chromium-review.googlesource.com/c/1268344
Commit-Queue: Gauthier Ambard <gambard@chromium.org>
Reviewed-by: Eugene But <eugenebut@chromium.org>
Reviewed-by: Stepan Khapugin <stkhapugin@chromium.org>
Cr-Commit-Position: refs/heads/master@{#599155}
diff --git a/ios/chrome/browser/ui/browser_view_controller.mm b/ios/chrome/browser/ui/browser_view_controller.mm
index 6ae837c..65cb249 100644
--- a/ios/chrome/browser/ui/browser_view_controller.mm
+++ b/ios/chrome/browser/ui/browser_view_controller.mm
@@ -4740,6 +4740,29 @@
   [nativeController focusFakebox];
 }
 
+- (void)unfocusOmniboxAndSwitchToTabWithURL:(const GURL&)URL {
+  // TODO(crbug.com/893121): Add animations.
+
+  // Cancelling the omnibox edit makes |URL| unsafe as it is not longer
+  // retained.
+  GURL retainedURL = URL;
+  [self.dispatcher cancelOmniboxEdit];
+
+  // TODO(crbug.com/893121): This should probably live in the WebState.
+  WebStateList* webStateList = self.tabModel.webStateList;
+  web::WebState* currentWebState = webStateList->GetActiveWebState();
+
+  for (NSInteger index = 0; index < webStateList->count(); index++) {
+    web::WebState* webState = webStateList->GetWebStateAt(index);
+
+    if (webState != currentWebState &&
+        retainedURL == webState->GetVisibleURL()) {
+      self.tabModel.webStateList->ActivateWebStateAt(index);
+      return;
+    }
+  }
+}
+
 #pragma mark - TabModelObserver methods
 
 // Observer method, tab inserted.
diff --git a/ios/chrome/browser/ui/browser_view_controller_unittest.mm b/ios/chrome/browser/ui/browser_view_controller_unittest.mm
index 3b080bf..dc30152 100644
--- a/ios/chrome/browser/ui/browser_view_controller_unittest.mm
+++ b/ios/chrome/browser/ui/browser_view_controller_unittest.mm
@@ -25,6 +25,7 @@
 #include "ios/chrome/browser/sessions/ios_chrome_tab_restore_service_factory.h"
 #import "ios/chrome/browser/snapshots/snapshot_tab_helper.h"
 #import "ios/chrome/browser/tabs/tab.h"
+#import "ios/chrome/browser/tabs/tab_helper_util.h"
 #import "ios/chrome/browser/tabs/tab_model.h"
 #import "ios/chrome/browser/tabs/tab_model_observer.h"
 #import "ios/chrome/browser/tabs/tab_private.h"
@@ -42,6 +43,7 @@
 #import "ios/chrome/browser/web/error_page_content.h"
 #include "ios/chrome/browser/web_state_list/fake_web_state_list_delegate.h"
 #include "ios/chrome/browser/web_state_list/web_state_list.h"
+#import "ios/chrome/browser/web_state_list/web_state_opener.h"
 #import "ios/chrome/browser/web_state_list/web_usage_enabler/web_state_list_web_usage_enabler.h"
 #import "ios/chrome/browser/web_state_list/web_usage_enabler/web_state_list_web_usage_enabler_factory.h"
 #include "ios/chrome/grit/ios_strings.h"
@@ -51,7 +53,11 @@
 #import "ios/net/protocol_handler_util.h"
 #import "ios/testing/ocmock_complex_type_helper.h"
 #include "ios/web/public/referrer.h"
+#import "ios/web/public/test/fakes/fake_navigation_context.h"
+#import "ios/web/public/test/fakes/test_navigation_manager.h"
+#import "ios/web/public/test/fakes/test_web_state.h"
 #include "ios/web/public/test/test_web_thread_bundle.h"
+#import "ios/web/public/web_state/js/crw_js_injection_receiver.h"
 #import "ios/web/public/web_state/ui/crw_native_content_provider.h"
 #import "ios/web/web_state/ui/crw_web_controller.h"
 #import "ios/web/web_state/web_state_impl.h"
@@ -241,6 +247,18 @@
     BlockCleanupTest::TearDown();
   }
 
+  // Returns a new unique_ptr containing a test webstate.
+  std::unique_ptr<web::TestWebState> CreateTestWebState() {
+    auto web_state = std::make_unique<web::TestWebState>();
+    web_state->SetBrowserState(chrome_browser_state_.get());
+    web_state->SetNavigationManager(
+        std::make_unique<web::TestNavigationManager>());
+    id mockJsInjectionReceiver = OCMClassMock([CRWJSInjectionReceiver class]);
+    web_state->SetJSInjectionReceiver(mockJsInjectionReceiver);
+    AttachTabHelpers(web_state.get(), true);
+    return web_state;
+  }
+
   MOCK_METHOD0(OnCompletionCalled, void());
 
   web::TestWebThreadBundle thread_bundle_;
@@ -256,6 +274,33 @@
   UIWindow* window_;
 };
 
+TEST_F(BrowserViewControllerTest, TestSwitchToTab) {
+  WebStateList* web_state_list = tabModel_.webStateList;
+  ASSERT_EQ(0, web_state_list->count());
+
+  std::unique_ptr<web::TestWebState> web_state = CreateTestWebState();
+  web::WebState* web_state_ptr = web_state.get();
+  web_state->SetCurrentURL(GURL("http://test/1"));
+  web_state_list->InsertWebState(0, std::move(web_state),
+                                 WebStateList::INSERT_FORCE_INDEX,
+                                 WebStateOpener());
+
+  std::unique_ptr<web::TestWebState> web_state_2 = CreateTestWebState();
+  web::WebState* web_state_ptr_2 = web_state_2.get();
+  GURL url("http://test/2");
+  web_state_2->SetCurrentURL(url);
+  web_state_list->InsertWebState(1, std::move(web_state_2),
+                                 WebStateList::INSERT_FORCE_INDEX,
+                                 WebStateOpener());
+
+  web_state_list->ActivateWebStateAt(0);
+
+  ASSERT_EQ(web_state_ptr, web_state_list->GetActiveWebState());
+
+  [bvc_.dispatcher unfocusOmniboxAndSwitchToTabWithURL:url];
+  EXPECT_EQ(web_state_ptr_2, web_state_list->GetActiveWebState());
+}
+
 TEST_F(BrowserViewControllerTest, TestTabSelected) {
   [bvc_ tabSelected:tab_ notifyToolbar:YES];
   EXPECT_EQ([[tab_ view] superview], [bvc_ contentArea]);
diff --git a/ios/chrome/browser/ui/commands/browser_commands.h b/ios/chrome/browser/ui/commands/browser_commands.h
index 8e91139..8acc5372 100644
--- a/ios/chrome/browser/ui/commands/browser_commands.h
+++ b/ios/chrome/browser/ui/commands/browser_commands.h
@@ -14,6 +14,7 @@
 #import "ios/chrome/browser/ui/commands/qr_scanner_commands.h"
 #import "ios/chrome/browser/ui/commands/snackbar_commands.h"
 
+class GURL;
 @class OpenNewTabCommand;
 @class ReadingListAddCommand;
 
@@ -116,6 +117,9 @@
 // omnibox.
 - (void)focusFakebox;
 
+// Unfocus omnibox then switch to the first tab displaying |URL|.
+- (void)unfocusOmniboxAndSwitchToTabWithURL:(const GURL&)URL;
+
 @end
 
 #endif  // IOS_CHROME_BROWSER_UI_COMMANDS_BROWSER_COMMANDS_H_
diff --git a/ios/chrome/browser/ui/omnibox/autocomplete_result_consumer.h b/ios/chrome/browser/ui/omnibox/autocomplete_result_consumer.h
index 06a4ee45..9658e21 100644
--- a/ios/chrome/browser/ui/omnibox/autocomplete_result_consumer.h
+++ b/ios/chrome/browser/ui/omnibox/autocomplete_result_consumer.h
@@ -22,7 +22,7 @@
 // Tells the delegate when a suggestion in|row| was chosen for appending to
 // omnibox.
 - (void)autocompleteResultConsumer:(id<AutocompleteResultConsumer>)sender
-          didSelectRowForAppending:(NSUInteger)row;
+        didTapTrailingButtonForRow:(NSUInteger)row;
 // Tells the delegate when a suggestion in |row| was removed.
 - (void)autocompleteResultConsumer:(id<AutocompleteResultConsumer>)sender
            didSelectRowForDeletion:(NSUInteger)row;
diff --git a/ios/chrome/browser/ui/omnibox/popup/BUILD.gn b/ios/chrome/browser/ui/omnibox/popup/BUILD.gn
index d92c3c5f..374dcdc5 100644
--- a/ios/chrome/browser/ui/omnibox/popup/BUILD.gn
+++ b/ios/chrome/browser/ui/omnibox/popup/BUILD.gn
@@ -52,6 +52,7 @@
     "self_sizing_table_view.mm",
   ]
   deps = [
+    "resources:omnibox_popup_tab_match",
     "//base",
     "//components/image_fetcher/ios",
     "//components/omnibox/browser",
@@ -73,9 +74,12 @@
   ]
   deps = [
     ":popup",
+    ":popup_internal",
     "//base",
+    "//components/omnibox/browser",
     "//ios/chrome/app/strings",
     "//ios/chrome/browser",
+    "//ios/chrome/browser/ui/omnibox:omnibox_internal",
     "//testing/gtest",
     "//ui/base",
   ]
diff --git a/ios/chrome/browser/ui/omnibox/popup/omnibox_popup_coordinator.mm b/ios/chrome/browser/ui/omnibox/popup/omnibox_popup_coordinator.mm
index 189072e3..20d31d8 100644
--- a/ios/chrome/browser/ui/omnibox/popup/omnibox_popup_coordinator.mm
+++ b/ios/chrome/browser/ui/omnibox/popup/omnibox_popup_coordinator.mm
@@ -61,6 +61,7 @@
   self.mediator =
       [[OmniboxPopupMediator alloc] initWithFetcher:std::move(imageFetcher)
                                            delegate:_popupView.get()];
+  self.mediator.dispatcher = (id<BrowserCommands>)self.dispatcher;
   self.popupViewController = [[OmniboxPopupViewController alloc] init];
   self.popupViewController.incognito = self.browserState->IsOffTheRecord();
 
diff --git a/ios/chrome/browser/ui/omnibox/popup/omnibox_popup_mediator.h b/ios/chrome/browser/ui/omnibox/popup/omnibox_popup_mediator.h
index 625210e9..16997a9 100644
--- a/ios/chrome/browser/ui/omnibox/popup/omnibox_popup_mediator.h
+++ b/ios/chrome/browser/ui/omnibox/popup/omnibox_popup_mediator.h
@@ -11,6 +11,7 @@
 #import "ios/chrome/browser/ui/omnibox/autocomplete_result_consumer.h"
 #import "ios/chrome/browser/ui/omnibox/image_retriever.h"
 
+@protocol BrowserCommands;
 @class OmniboxPopupPresenter;
 
 namespace image_fetcher {
@@ -48,6 +49,7 @@
 // Updates the popup with the |results|.
 - (void)updateWithResults:(const AutocompleteResult&)results;
 
+@property(nonatomic, weak) id<BrowserCommands> dispatcher;
 @property(nonatomic, weak) id<AutocompleteResultConsumer> consumer;
 @property(nonatomic, assign, getter=isIncognito) BOOL incognito;
 // Whether the popup is open.
diff --git a/ios/chrome/browser/ui/omnibox/popup/omnibox_popup_mediator.mm b/ios/chrome/browser/ui/omnibox/popup/omnibox_popup_mediator.mm
index ded70cc..e7a0859 100644
--- a/ios/chrome/browser/ui/omnibox/popup/omnibox_popup_mediator.mm
+++ b/ios/chrome/browser/ui/omnibox/popup/omnibox_popup_mediator.mm
@@ -10,6 +10,7 @@
 #include "components/omnibox/browser/autocomplete_input.h"
 #include "components/omnibox/browser/autocomplete_match.h"
 #include "components/omnibox/browser/autocomplete_result.h"
+#import "ios/chrome/browser/ui/commands/browser_commands.h"
 #import "ios/chrome/browser/ui/omnibox/autocomplete_match_formatter.h"
 #import "ios/chrome/browser/ui/omnibox/popup/omnibox_popup_presenter.h"
 
@@ -113,19 +114,22 @@
 }
 
 - (void)autocompleteResultConsumer:(id<AutocompleteResultConsumer>)sender
-          didSelectRowForAppending:(NSUInteger)row {
+        didTapTrailingButtonForRow:(NSUInteger)row {
   const AutocompleteMatch& match =
       ((const AutocompleteResult&)_currentResult).match_at(row);
 
-  if (AutocompleteMatch::IsSearchType(match.type)) {
-    base::RecordAction(
-        base::UserMetricsAction("MobileOmniboxRefineSuggestion.Search"));
+  if (match.has_tab_match) {
+    [self.dispatcher unfocusOmniboxAndSwitchToTabWithURL:match.destination_url];
   } else {
-    base::RecordAction(
-        base::UserMetricsAction("MobileOmniboxRefineSuggestion.Url"));
+    if (AutocompleteMatch::IsSearchType(match.type)) {
+      base::RecordAction(
+          base::UserMetricsAction("MobileOmniboxRefineSuggestion.Search"));
+    } else {
+      base::RecordAction(
+          base::UserMetricsAction("MobileOmniboxRefineSuggestion.Url"));
+    }
+    _delegate->OnMatchSelectedForAppending(match);
   }
-
-  _delegate->OnMatchSelectedForAppending(match);
 }
 
 - (void)autocompleteResultConsumer:(id<AutocompleteResultConsumer>)sender
diff --git a/ios/chrome/browser/ui/omnibox/popup/omnibox_popup_row.h b/ios/chrome/browser/ui/omnibox/popup/omnibox_popup_row.h
index d0e5196..74a208efd 100644
--- a/ios/chrome/browser/ui/omnibox/popup/omnibox_popup_row.h
+++ b/ios/chrome/browser/ui/omnibox/popup/omnibox_popup_row.h
@@ -24,8 +24,11 @@
 
 @property(nonatomic, readonly, strong) UIImageView* imageView;
 @property(nonatomic, readonly, strong) UIImageView* answerImageView;
-@property(nonatomic, readonly, strong) UIButton* appendButton;
+@property(nonatomic, readonly, strong) UIButton* trailingButton;
 @property(nonatomic, assign) CGFloat rowHeight;
+// Whether this row is displaying a TabMatch. If YES, the trailing icon is
+// updated to reflect that.
+@property(nonatomic, assign, getter=isTabMatch) BOOL tabMatch;
 
 // Initialize the row with the given incognito state. The colors and styling are
 // dependent on whether or not the row is displayed in incognito mode.
diff --git a/ios/chrome/browser/ui/omnibox/popup/omnibox_popup_row.mm b/ios/chrome/browser/ui/omnibox/popup/omnibox_popup_row.mm
index 44e31b0..ea0f8a3 100644
--- a/ios/chrome/browser/ui/omnibox/popup/omnibox_popup_row.mm
+++ b/ios/chrome/browser/ui/omnibox/popup/omnibox_popup_row.mm
@@ -20,8 +20,8 @@
 // Side (w or h) length for the leading image view.
 const CGFloat kImageViewSizeUIRefresh = 28.0;
 const CGFloat kImageViewCornerRadiusUIRefresh = 7.0;
-const CGFloat kAppendButtonTrailingMargin = 4;
-const CGFloat kAppendButtonSize = 48.0;
+const CGFloat kTrailingButtonTrailingMargin = 4;
+const CGFloat kTrailingButtonSize = 48.0;
 }
 
 @interface OmniboxPopupRow () {
@@ -29,7 +29,7 @@
 }
 
 // Set the append button normal and highlighted images.
-- (void)updateAppendButtonImages;
+- (void)updateTrailingButtonImages;
 
 @end
 
@@ -38,7 +38,7 @@
 @synthesize textTruncatingLabel = _textTruncatingLabel;
 @synthesize detailTruncatingLabel = _detailTruncatingLabel;
 @synthesize detailAnswerLabel = _detailAnswerLabel;
-@synthesize appendButton = _appendButton;
+@synthesize trailingButton = _trailingButton;
 @synthesize answerImageView = _answerImageView;
 @synthesize imageView = _imageView;
 @synthesize rowHeight = _rowHeight;
@@ -73,12 +73,12 @@
     _detailAnswerLabel.lineBreakMode = NSLineBreakByTruncatingTail;
     [self.contentView addSubview:_detailAnswerLabel];
 
-    _appendButton = [UIButton buttonWithType:UIButtonTypeCustom];
-    [_appendButton setContentMode:UIViewContentModeRight];
-    [self updateAppendButtonImages];
+    _trailingButton = [UIButton buttonWithType:UIButtonTypeCustom];
+    [_trailingButton setContentMode:UIViewContentModeRight];
+    [self updateTrailingButtonImages];
     // TODO(justincohen): Consider using the UITableViewCell's accessory view.
     // The current implementation is from before using a UITableViewCell.
-    [self.contentView addSubview:_appendButton];
+    [self.contentView addSubview:_trailingButton];
 
     // Before UI Refresh, the leading icon is only displayed on iPad. In UI
     // Refresh, it's only in Regular x Regular size class.
@@ -134,11 +134,11 @@
 
   LayoutRect trailingAccessoryLayout =
       LayoutRectMake(CGRectGetWidth(self.contentView.bounds) -
-                         kAppendButtonSize - kAppendButtonTrailingMargin,
+                         kTrailingButtonSize - kTrailingButtonTrailingMargin,
                      CGRectGetWidth(self.contentView.bounds),
-                     floor((_rowHeight - kAppendButtonSize) / 2),
-                     kAppendButtonSize, kAppendButtonSize);
-  _appendButton.frame = LayoutRectGetRect(trailingAccessoryLayout);
+                     floor((_rowHeight - kTrailingButtonSize) / 2),
+                     kTrailingButtonSize, kTrailingButtonSize);
+  _trailingButton.frame = LayoutRectGetRect(trailingAccessoryLayout);
 }
 
 - (void)updateLeadingImage:(UIImage*)image {
@@ -173,27 +173,38 @@
   [self updateHighlightBackground:highlighted];
 }
 
-- (void)updateAppendButtonImages {
-  int appendResourceID = _incognito
-                             ? IDR_IOS_OMNIBOX_KEYBOARD_VIEW_APPEND_INCOGNITO
-                             : IDR_IOS_OMNIBOX_KEYBOARD_VIEW_APPEND;
-  UIImage* appendImage = NativeReversableImage(appendResourceID, YES);
+- (void)setTabMatch:(BOOL)tabMatch {
+  _tabMatch = tabMatch;
+  [self updateTrailingButtonImages];
+}
+
+- (void)updateTrailingButtonImages {
+  UIImage* appendImage = nil;
+  if (self.tabMatch) {
+    appendImage = [UIImage imageNamed:@"omnibox_popup_tab_match"];
+  } else {
+    int appendResourceID = _incognito
+                               ? IDR_IOS_OMNIBOX_KEYBOARD_VIEW_APPEND_INCOGNITO
+                               : IDR_IOS_OMNIBOX_KEYBOARD_VIEW_APPEND;
+    appendImage = NativeReversableImage(appendResourceID, YES);
+  }
   if (IsUIRefreshPhase1Enabled()) {
     appendImage =
         [appendImage imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate];
-    _appendButton.tintColor = _incognito ? [UIColor colorWithWhite:1 alpha:0.5]
-                                         : [UIColor colorWithWhite:0 alpha:0.3];
+    _trailingButton.tintColor = _incognito
+                                    ? [UIColor colorWithWhite:1 alpha:0.5]
+                                    : [UIColor colorWithWhite:0 alpha:0.3];
   } else {
     int appendSelectedResourceID =
         _incognito ? IDR_IOS_OMNIBOX_KEYBOARD_VIEW_APPEND_INCOGNITO_HIGHLIGHTED
                    : IDR_IOS_OMNIBOX_KEYBOARD_VIEW_APPEND_HIGHLIGHTED;
     UIImage* appendImageSelected =
         NativeReversableImage(appendSelectedResourceID, YES);
-    [_appendButton setImage:appendImageSelected
-                   forState:UIControlStateHighlighted];
+    [_trailingButton setImage:appendImageSelected
+                     forState:UIControlStateHighlighted];
   }
 
-  [_appendButton setImage:appendImage forState:UIControlStateNormal];
+  [_trailingButton setImage:appendImage forState:UIControlStateNormal];
 }
 
 - (NSString*)accessibilityLabel {
diff --git a/ios/chrome/browser/ui/omnibox/popup/omnibox_popup_view_controller.mm b/ios/chrome/browser/ui/omnibox/popup/omnibox_popup_view_controller.mm
index 16bb342..05ebb66 100644
--- a/ios/chrome/browser/ui/omnibox/popup/omnibox_popup_view_controller.mm
+++ b/ios/chrome/browser/ui/omnibox/popup/omnibox_popup_view_controller.mm
@@ -153,10 +153,10 @@
         [NSString stringWithFormat:@"omnibox suggestion %i", i];
     row.autoresizingMask = UIViewAutoresizingFlexibleWidth;
     [rowsBuilder addObject:row];
-    [row.appendButton addTarget:self
-                         action:@selector(appendButtonTapped:)
-               forControlEvents:UIControlEventTouchUpInside];
-    [row.appendButton setTag:i];
+    [row.trailingButton addTarget:self
+                           action:@selector(trailingButtonTapped:)
+                 forControlEvents:UIControlEventTouchUpInside];
+    [row.trailingButton setTag:i];
     row.rowHeight = kRowHeight;
   }
   _rows = [rowsBuilder copy];
@@ -284,7 +284,7 @@
   const CGFloat kDetailCellTopPadding = 26;
   const CGFloat kTextLabelHeight = 24;
   const CGFloat kTextDetailLabelHeight = 22;
-  const CGFloat kAppendButtonWidth = 40;
+  const CGFloat kTrailingButtonWidth = 40;
   const CGFloat kAnswerLabelHeight = 36;
   const CGFloat kAnswerImageWidth = 30;
   const CGFloat kAnswerImageLeftPadding = -1;
@@ -308,7 +308,7 @@
         kTextCellLeadingPadding + kAnswerImageLeftPadding;
     if (alignmentRight) {
       imageLeftPadding =
-          row.frame.size.width - (kAnswerImageWidth + kAppendButtonWidth);
+          row.frame.size.width - (kAnswerImageWidth + kTrailingButtonWidth);
     }
     CGFloat imageTopPadding = kDetailCellTopPadding + kAnswerImageTopPadding;
     row.answerImageView.frame =
@@ -396,22 +396,24 @@
     [row updateLeadingImage:image];
   }
 
+  row.tabMatch = match.isTabMatch;
+
   // Show append button for search history/search suggestions as the right
   // control element (aka an accessory element of a table view cell).
-  row.appendButton.hidden = !match.isAppendable;
-  [row.appendButton cancelTrackingWithEvent:nil];
+  row.trailingButton.hidden = !match.isAppendable && !match.isTabMatch;
+  [row.trailingButton cancelTrackingWithEvent:nil];
 
   // If a right accessory element is present or the text alignment is right
   // aligned, adjust the width to align with the accessory element.
   if (match.isAppendable || alignmentRight) {
     LayoutRect layout =
         LayoutRectForRectInBoundingRect(textLabel.frame, self.view.frame);
-    layout.size.width -= kAppendButtonWidth;
+    layout.size.width -= kTrailingButtonWidth;
     textLabel.frame = LayoutRectGetRect(layout);
     layout =
         LayoutRectForRectInBoundingRect(detailTextLabel.frame, self.view.frame);
     layout.size.width -=
-        kAppendButtonWidth + (match.hasImage ? answerImagePadding : 0);
+        kTrailingButtonWidth + (match.hasImage ? answerImagePadding : 0);
     detailTextLabel.frame = LayoutRectGetRect(layout);
   }
 
@@ -524,9 +526,10 @@
 #pragma mark -
 #pragma mark Action for append UIButton
 
-- (void)appendButtonTapped:(id)sender {
+- (void)trailingButtonTapped:(id)sender {
   NSUInteger row = [sender tag];
-  [self.delegate autocompleteResultConsumer:self didSelectRowForAppending:row];
+  [self.delegate autocompleteResultConsumer:self
+                 didTapTrailingButtonForRow:row];
 }
 
 #pragma mark -
diff --git a/ios/chrome/browser/ui/omnibox/popup/omnibox_popup_view_controller_unittest.mm b/ios/chrome/browser/ui/omnibox/popup/omnibox_popup_view_controller_unittest.mm
index 3117c0e..e864000 100644
--- a/ios/chrome/browser/ui/omnibox/popup/omnibox_popup_view_controller_unittest.mm
+++ b/ios/chrome/browser/ui/omnibox/popup/omnibox_popup_view_controller_unittest.mm
@@ -4,6 +4,9 @@
 
 #import "ios/chrome/browser/ui/omnibox/popup/omnibox_popup_view_controller.h"
 
+#include "components/omnibox/browser/autocomplete_match.h"
+#import "ios/chrome/browser/ui/omnibox/autocomplete_match_formatter.h"
+#import "ios/chrome/browser/ui/omnibox/popup/omnibox_popup_row.h"
 #include "testing/gtest_mac.h"
 #include "testing/platform_test.h"
 
@@ -23,14 +26,14 @@
   OmniboxPopupViewController* popup_view_controller_;
 };
 
-TEST_F(OmniboxPopupViewControllerTest, hasCellsWhenShortcutsEnabled) {
+TEST_F(OmniboxPopupViewControllerTest, HasCellsWhenShortcutsEnabled) {
   // This test makes an assumption that this view controller is a datasource for
   // a table view. Rewrite this test if this is not the case anymore.
   EXPECT_TRUE([popup_view_controller_
       conformsToProtocol:@protocol(UITableViewDataSource)]);
   id<UITableViewDataSource> datasource =
       (id<UITableViewDataSource>)popup_view_controller_;
-  UITableView* tableView = [[UITableView alloc] init];
+  UITableView* table_view = [[UITableView alloc] init];
 
   // A stub view controller.
   UIViewController* shortcutsViewController = [[UIViewController alloc] init];
@@ -40,17 +43,52 @@
 
   // Check that the shorcuts row doesn't appear.
   [popup_view_controller_ updateMatches:@[] withAnimation:NO];
-  EXPECT_EQ([datasource tableView:tableView numberOfRowsInSection:0], 0);
+  EXPECT_EQ([datasource tableView:table_view numberOfRowsInSection:0], 0);
 
   // Enable shortcuts and verify they appear. When enabling, the view controller
   // has to be non-nil.
   popup_view_controller_.shortcutsViewController = shortcutsViewController;
   popup_view_controller_.shortcutsEnabled = YES;
-  EXPECT_EQ([datasource tableView:tableView numberOfRowsInSection:0], 1);
+  EXPECT_EQ([datasource tableView:table_view numberOfRowsInSection:0], 1);
 
   // Disable and verify it disappears again.
   popup_view_controller_.shortcutsEnabled = NO;
-  EXPECT_EQ([datasource tableView:tableView numberOfRowsInSection:0], 0);
+  EXPECT_EQ([datasource tableView:table_view numberOfRowsInSection:0], 0);
+}
+
+TEST_F(OmniboxPopupViewControllerTest, HasTabMatch) {
+  EXPECT_TRUE([popup_view_controller_
+      conformsToProtocol:@protocol(UITableViewDataSource)]);
+  id<UITableViewDataSource> datasource =
+      (id<UITableViewDataSource>)popup_view_controller_;
+  UITableView* table_view = [[UITableView alloc] init];
+
+  // Check that if the match has a tab match, the cell's trailing button is
+  // visible.
+  AutocompleteMatch match;
+  match.has_tab_match = true;
+  AutocompleteMatchFormatter* formatter =
+      [[AutocompleteMatchFormatter alloc] initWithMatch:match];
+  [popup_view_controller_ updateMatches:@[ formatter ] withAnimation:NO];
+
+  EXPECT_EQ([datasource tableView:table_view numberOfRowsInSection:0], 1);
+  OmniboxPopupRow* cell = static_cast<OmniboxPopupRow*>([datasource
+                  tableView:table_view
+      cellForRowAtIndexPath:[NSIndexPath indexPathForRow:0 inSection:0]]);
+
+  EXPECT_FALSE(cell.trailingButton.hidden);
+
+  // Check that it is not the case if the tab match isn't visible.
+  match.has_tab_match = false;
+  formatter = [[AutocompleteMatchFormatter alloc] initWithMatch:match];
+  [popup_view_controller_ updateMatches:@[ formatter ] withAnimation:NO];
+
+  EXPECT_EQ([datasource tableView:table_view numberOfRowsInSection:0], 1);
+  cell = static_cast<OmniboxPopupRow*>([datasource
+                  tableView:table_view
+      cellForRowAtIndexPath:[NSIndexPath indexPathForRow:0 inSection:0]]);
+
+  EXPECT_TRUE(cell.trailingButton.hidden);
 }
 
 }  // namespace
diff --git a/ios/chrome/browser/ui/omnibox/popup/resources/BUILD.gn b/ios/chrome/browser/ui/omnibox/popup/resources/BUILD.gn
new file mode 100644
index 0000000..c7ac8e4
--- /dev/null
+++ b/ios/chrome/browser/ui/omnibox/popup/resources/BUILD.gn
@@ -0,0 +1,14 @@
+# Copyright 2017 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+import("//build/config/ios/asset_catalog.gni")
+
+imageset("omnibox_popup_tab_match") {
+  sources = [
+    "omnibox_popup_tab_match.imageset/Contents.json",
+    "omnibox_popup_tab_match.imageset/omnibox_popup_tab_match.png",
+    "omnibox_popup_tab_match.imageset/omnibox_popup_tab_match@2x.png",
+    "omnibox_popup_tab_match.imageset/omnibox_popup_tab_match@3x.png",
+  ]
+}
diff --git a/ios/chrome/browser/ui/omnibox/popup/resources/omnibox_popup_tab_match.imageset/Contents.json b/ios/chrome/browser/ui/omnibox/popup/resources/omnibox_popup_tab_match.imageset/Contents.json
new file mode 100644
index 0000000..3b1fa9d
--- /dev/null
+++ b/ios/chrome/browser/ui/omnibox/popup/resources/omnibox_popup_tab_match.imageset/Contents.json
@@ -0,0 +1,23 @@
+{
+    "images": [
+        {
+            "idiom": "universal",
+            "scale": "1x",
+            "filename": "omnibox_popup_tab_match.png"
+        },
+        {
+            "idiom": "universal",
+            "scale": "2x",
+            "filename": "omnibox_popup_tab_match@2x.png"
+        },
+        {
+            "idiom": "universal",
+            "scale": "3x",
+            "filename": "omnibox_popup_tab_match@3x.png"
+        }
+    ],
+    "info": {
+        "version": 1,
+        "author": "xcode"
+    }
+}
diff --git a/ios/chrome/browser/ui/omnibox/popup/resources/omnibox_popup_tab_match.imageset/omnibox_popup_tab_match.png b/ios/chrome/browser/ui/omnibox/popup/resources/omnibox_popup_tab_match.imageset/omnibox_popup_tab_match.png
new file mode 100644
index 0000000..0ee350d
--- /dev/null
+++ b/ios/chrome/browser/ui/omnibox/popup/resources/omnibox_popup_tab_match.imageset/omnibox_popup_tab_match.png
Binary files differ
diff --git a/ios/chrome/browser/ui/omnibox/popup/resources/omnibox_popup_tab_match.imageset/omnibox_popup_tab_match@2x.png b/ios/chrome/browser/ui/omnibox/popup/resources/omnibox_popup_tab_match.imageset/omnibox_popup_tab_match@2x.png
new file mode 100644
index 0000000..3c5140b
--- /dev/null
+++ b/ios/chrome/browser/ui/omnibox/popup/resources/omnibox_popup_tab_match.imageset/omnibox_popup_tab_match@2x.png
Binary files differ
diff --git a/ios/chrome/browser/ui/omnibox/popup/resources/omnibox_popup_tab_match.imageset/omnibox_popup_tab_match@3x.png b/ios/chrome/browser/ui/omnibox/popup/resources/omnibox_popup_tab_match.imageset/omnibox_popup_tab_match@3x.png
new file mode 100644
index 0000000..9bf6189
--- /dev/null
+++ b/ios/chrome/browser/ui/omnibox/popup/resources/omnibox_popup_tab_match.imageset/omnibox_popup_tab_match@3x.png
Binary files differ