[iOS][Omnibox] Omnibox paste to search refactoring

This CL moves the UIPasteboard interaction and paste to search logic to
the omnibox_mediator.

Bug: 1356514
Change-Id: Iaec08c034d52d76cd3eb07cea962bef1df784a04
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/3850470
Reviewed-by: Gauthier Ambard <gambard@chromium.org>
Commit-Queue: Christian Xu <christianxu@chromium.org>
Auto-Submit: Christian Xu <christianxu@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1043135}
diff --git a/ios/chrome/browser/ui/omnibox/omnibox_coordinator.mm b/ios/chrome/browser/ui/omnibox/omnibox_coordinator.mm
index de2a47a..f153bfd 100644
--- a/ios/chrome/browser/ui/omnibox/omnibox_coordinator.mm
+++ b/ios/chrome/browser/ui/omnibox/omnibox_coordinator.mm
@@ -53,7 +53,7 @@
 #error "This file requires ARC support."
 #endif
 
-@interface OmniboxCoordinator () <OmniboxViewControllerDelegate>
+@interface OmniboxCoordinator () <OmniboxViewControllerTextInputDelegate>
 // Object taking care of adding the accessory views to the keyboard.
 @property(nonatomic, strong)
     OmniboxAssistiveKeyboardDelegateImpl* keyboardDelegate;
@@ -96,13 +96,8 @@
 
   self.viewController.defaultLeadingImage =
       GetOmniboxSuggestionIcon(DEFAULT_FAVICON);
-  // TODO(crbug.com/1045047): Use HandlerForProtocol after commands protocol
-  // clean up.
-  self.viewController.dispatcher =
-      static_cast<id<BrowserCommands, LoadQueryCommands, OmniboxCommands>>(
-          self.browser->GetCommandDispatcher());
-  self.viewController.delegate = self;
-  self.mediator = [[OmniboxMediator alloc] init];
+  self.viewController.textInputDelegate = self;
+  self.mediator = [[OmniboxMediator alloc] initWithIncognito:isIncognito];
   self.mediator.templateURLService =
       ios::TemplateURLServiceFactory::GetForBrowserState(
           self.browser->GetBrowserState());
@@ -110,6 +105,15 @@
       IOSChromeFaviconLoaderFactory::GetForBrowserState(
           self.browser->GetBrowserState());
   self.mediator.consumer = self.viewController;
+  self.mediator.omniboxCommandsHandler =
+      HandlerForProtocol(self.browser->GetCommandDispatcher(), OmniboxCommands);
+  self.mediator.loadQueryCommandsHandler = HandlerForProtocol(
+      self.browser->GetCommandDispatcher(), LoadQueryCommands);
+  self.mediator.sceneState =
+      SceneStateBrowserAgent::FromBrowser(self.browser)->GetSceneState();
+  self.mediator.URLLoadingBrowserAgent =
+      UrlLoadingBrowserAgent::FromBrowser(self.browser);
+  self.viewController.pasteDelegate = self.mediator;
 
   DCHECK(self.editController);
 
@@ -258,38 +262,13 @@
   return self.viewController.textField;
 }
 
-#pragma mark - OmniboxViewControllerDelegate
+#pragma mark - OmniboxViewControllerTextInputDelegate
 
 - (void)omniboxViewControllerTextInputModeDidChange:
     (OmniboxViewController*)omniboxViewController {
   _editView->UpdatePopupAppearance();
 }
 
-- (void)omniboxViewControllerUserDidVisitCopiedLink:
-    (OmniboxViewController*)omniboxViewController {
-  // Don't log pastes in incognito.
-  if (self.browser->GetBrowserState()->IsOffTheRecord()) {
-    return;
-  }
-
-  SceneState* sceneState =
-      SceneStateBrowserAgent::FromBrowser(self.browser)->GetSceneState();
-  DefaultBrowserSceneAgent* agent =
-      [DefaultBrowserSceneAgent agentFromScene:sceneState];
-  [agent.nonModalScheduler logUserPastedInOmnibox];
-}
-
-- (void)omniboxViewControllerSearchImage:(UIImage*)image {
-  DCHECK(image);
-  web::NavigationManager::WebLoadParams webParams =
-      ImageSearchParamGenerator::LoadParamsForImage(
-          image, ios::TemplateURLServiceFactory::GetForBrowserState(
-                     self.browser->GetBrowserState()));
-  UrlLoadParams params = UrlLoadParams::InCurrentTab(webParams);
-
-  UrlLoadingBrowserAgent::FromBrowser(self.browser)->Load(params);
-}
-
 #pragma mark - private
 
 // Convenience accessor.
diff --git a/ios/chrome/browser/ui/omnibox/omnibox_mediator.h b/ios/chrome/browser/ui/omnibox/omnibox_mediator.h
index 5b19d65..287f759 100644
--- a/ios/chrome/browser/ui/omnibox/omnibox_mediator.h
+++ b/ios/chrome/browser/ui/omnibox/omnibox_mediator.h
@@ -7,25 +7,41 @@
 
 #import <UIKit/UIKit.h>
 
+#import "ios/chrome/browser/ui/omnibox/omnibox_view_controller.h"
 #import "ios/chrome/browser/ui/omnibox/popup/popup_match_preview_delegate.h"
 
-@protocol OmniboxConsumer;
 class FaviconLoader;
+@protocol LoadQueryCommands;
+@protocol OmniboxCommands;
+@protocol OmniboxConsumer;
+@class SceneState;
 class TemplateURLService;
+class UrlLoadingBrowserAgent;
 
 // A mediator object that updates the omnibox according to the model changes.
-@interface OmniboxMediator : NSObject <PopupMatchPreviewDelegate>
+@interface OmniboxMediator
+    : NSObject <OmniboxViewControllerPasteDelegate, PopupMatchPreviewDelegate>
+
+- (instancetype)initWithIncognito:(BOOL)isIncognito NS_DESIGNATED_INITIALIZER;
+- (instancetype)init NS_UNAVAILABLE;
 
 // The templateURLService used by this mediator to extract whether the default
 // search engine supports search-by-image.
 @property(nonatomic, assign) TemplateURLService* templateURLService;
+// The `URLLoadingBrowserAgent` used by this mediator to start search-by-image.
+@property(nonatomic, assign) UrlLoadingBrowserAgent* URLLoadingBrowserAgent;
 
 // The consumer for this object. This can change during the lifetime of this
 // object and may be nil.
 @property(nonatomic, weak) id<OmniboxConsumer> consumer;
 
+@property(nonatomic, weak) id<LoadQueryCommands> loadQueryCommandsHandler;
+@property(nonatomic, weak) id<OmniboxCommands> omniboxCommandsHandler;
+
 // The favicon loader.
 @property(nonatomic, assign) FaviconLoader* faviconLoader;
+// Scene state used by this mediator to log with DefaultBrowserSceneAgent.
+@property(nonatomic, weak) SceneState* sceneState;
 
 @end
 
diff --git a/ios/chrome/browser/ui/omnibox/omnibox_mediator.mm b/ios/chrome/browser/ui/omnibox/omnibox_mediator.mm
index 4bd7a3e..8c27acf 100644
--- a/ios/chrome/browser/ui/omnibox/omnibox_mediator.mm
+++ b/ios/chrome/browser/ui/omnibox/omnibox_mediator.mm
@@ -4,27 +4,45 @@
 
 #import "ios/chrome/browser/ui/omnibox/omnibox_mediator.h"
 
+#import "base/metrics/user_metrics.h"
+#import "base/metrics/user_metrics_action.h"
 #include "base/strings/sys_string_conversions.h"
 #include "components/omnibox/browser/autocomplete_match.h"
+#import "components/open_from_clipboard/clipboard_recent_content.h"
 #import "ios/chrome/browser/favicon/favicon_loader.h"
 #import "ios/chrome/browser/net/crurl.h"
 #import "ios/chrome/browser/search_engines/search_engine_observer_bridge.h"
 #import "ios/chrome/browser/search_engines/search_engines_util.h"
+#import "ios/chrome/browser/ui/commands/load_query_commands.h"
+#import "ios/chrome/browser/ui/commands/omnibox_commands.h"
+#import "ios/chrome/browser/ui/default_promo/default_browser_promo_non_modal_scheduler.h"
+#import "ios/chrome/browser/ui/default_promo/default_browser_utils.h"
+#import "ios/chrome/browser/ui/main/default_browser_scene_agent.h"
+#import "ios/chrome/browser/ui/main/scene_state_browser_agent.h"
 #import "ios/chrome/browser/ui/omnibox/omnibox_consumer.h"
 #import "ios/chrome/browser/ui/omnibox/omnibox_util.h"
 #import "ios/chrome/browser/ui/omnibox/popup/autocomplete_suggestion.h"
 #include "ios/chrome/browser/ui/ui_feature_flags.h"
 #import "ios/chrome/browser/ui/util/uikit_ui_util.h"
+#import "ios/chrome/browser/url_loading/image_search_param_generator.h"
+#import "ios/chrome/browser/url_loading/url_loading_browser_agent.h"
+#import "ios/chrome/browser/url_loading/url_loading_params.h"
 #import "ios/chrome/common/ui/favicon/favicon_attributes.h"
 #import "ios/chrome/common/ui/favicon/favicon_constants.h"
 #import "ios/public/provider/chrome/browser/branded_images/branded_images_api.h"
+#import "ios/web/public/navigation/navigation_manager.h"
 
 #if !defined(__has_feature) || !__has_feature(objc_arc)
 #error "This file requires ARC support."
 #endif
 
+using base::UserMetricsAction;
+
 @interface OmniboxMediator () <SearchEngineObserving>
 
+// Is Browser incognito.
+@property(nonatomic, assign, readonly) BOOL isIncognito;
+
 // Whether the current default search engine supports search-by-image.
 @property(nonatomic, assign) BOOL searchEngineSupportsSearchByImage;
 
@@ -44,10 +62,11 @@
   std::unique_ptr<SearchEngineObserverBridge> _searchEngineObserver;
 }
 
-- (instancetype)init {
+- (instancetype)initWithIncognito:(BOOL)isIncognito {
   self = [super init];
   if (self) {
     _searchEngineSupportsSearchByImage = NO;
+    _isIncognito = isIncognito;
   }
   return self;
 }
@@ -259,4 +278,122 @@
   }];
 }
 
+#pragma mark - OmniboxViewControllerPasteDelegate
+
+- (void)didTapPasteToSearchButton:(NSArray<NSItemProvider*>*)itemProviders {
+  __weak __typeof(self) weakSelf = self;
+  auto textCompletion =
+      ^(__kindof id<NSItemProviderReading> providedItem, NSError* error) {
+        LogLikelyInterestedDefaultBrowserUserActivity(DefaultPromoTypeGeneral);
+        dispatch_async(dispatch_get_main_queue(), ^{
+          NSString* text = static_cast<NSString*>(providedItem);
+          if (text) {
+            [weakSelf.loadQueryCommandsHandler loadQuery:text immediately:YES];
+            [weakSelf.omniboxCommandsHandler cancelOmniboxEdit];
+          }
+        });
+      };
+  auto imageCompletion =
+      ^(__kindof id<NSItemProviderReading> providedItem, NSError* error) {
+        dispatch_async(dispatch_get_main_queue(), ^{
+          UIImage* image = static_cast<UIImage*>(providedItem);
+          if (image) {
+            [weakSelf loadImageQuery:image];
+            [weakSelf.omniboxCommandsHandler cancelOmniboxEdit];
+          }
+        });
+      };
+  for (NSItemProvider* itemProvider in itemProviders) {
+    if (self.searchEngineSupportsSearchByImage &&
+        [itemProvider canLoadObjectOfClass:[UIImage class]]) {
+      RecordAction(
+          UserMetricsAction("Mobile.OmniboxPasteButton.SearchCopiedImage"));
+      [itemProvider loadObjectOfClass:[UIImage class]
+                    completionHandler:imageCompletion];
+      break;
+    } else if ([itemProvider canLoadObjectOfClass:[NSURL class]]) {
+      RecordAction(
+          UserMetricsAction("Mobile.OmniboxPasteButton.SearchCopiedLink"));
+      [self logUserPasted];
+      // Load URL as a NSString to avoid further conversion.
+      [itemProvider loadObjectOfClass:[NSString class]
+                    completionHandler:textCompletion];
+      break;
+    } else if ([itemProvider canLoadObjectOfClass:[NSString class]]) {
+      RecordAction(
+          UserMetricsAction("Mobile.OmniboxPasteButton.SearchCopiedText"));
+      [itemProvider loadObjectOfClass:[NSString class]
+                    completionHandler:textCompletion];
+      break;
+    }
+  }
+}
+
+- (void)didTapVisitCopiedLink {
+  [self logUserPasted];
+  __weak __typeof(self) weakSelf = self;
+  ClipboardRecentContent::GetInstance()->GetRecentURLFromClipboard(
+      base::BindOnce(^(absl::optional<GURL> optionalURL) {
+        if (!optionalURL) {
+          return;
+        }
+        NSString* url = base::SysUTF8ToNSString(optionalURL.value().spec());
+        dispatch_async(dispatch_get_main_queue(), ^{
+          [weakSelf.loadQueryCommandsHandler loadQuery:url immediately:YES];
+          [weakSelf.omniboxCommandsHandler cancelOmniboxEdit];
+        });
+      }));
+}
+
+- (void)didTapSearchCopiedText {
+  __weak __typeof(self) weakSelf = self;
+  ClipboardRecentContent::GetInstance()->GetRecentTextFromClipboard(
+      base::BindOnce(^(absl::optional<std::u16string> optionalText) {
+        if (!optionalText) {
+          return;
+        }
+        NSString* query = base::SysUTF16ToNSString(optionalText.value());
+        dispatch_async(dispatch_get_main_queue(), ^{
+          [weakSelf.loadQueryCommandsHandler loadQuery:query immediately:YES];
+          [weakSelf.omniboxCommandsHandler cancelOmniboxEdit];
+        });
+      }));
+}
+
+- (void)didTapSearchCopiedImage {
+  __weak __typeof(self) weakSelf = self;
+  ClipboardRecentContent::GetInstance()->GetRecentImageFromClipboard(
+      base::BindOnce(^(absl::optional<gfx::Image> optionalImage) {
+        if (!optionalImage) {
+          return;
+        }
+        UIImage* image = optionalImage.value().ToUIImage();
+        [weakSelf loadImageQuery:image];
+      }));
+}
+
+#pragma mark - Private methods
+
+// Logs that user pasted a link into the omnibox.
+- (void)logUserPasted {
+  // Don't log pastes in incognito.
+  if (self.isIncognito) {
+    return;
+  }
+
+  DefaultBrowserSceneAgent* agent =
+      [DefaultBrowserSceneAgent agentFromScene:self.sceneState];
+  [agent.nonModalScheduler logUserPastedInOmnibox];
+}
+
+// Loads an image-search query with `image`.
+- (void)loadImageQuery:(UIImage*)image {
+  DCHECK(image);
+  web::NavigationManager::WebLoadParams webParams =
+      ImageSearchParamGenerator::LoadParamsForImage(image,
+                                                    self.templateURLService);
+  UrlLoadParams params = UrlLoadParams::InCurrentTab(webParams);
+  self.URLLoadingBrowserAgent->Load(params);
+}
+
 @end
diff --git a/ios/chrome/browser/ui/omnibox/omnibox_view_controller.h b/ios/chrome/browser/ui/omnibox/omnibox_view_controller.h
index f3d9808..60e7dbe7 100644
--- a/ios/chrome/browser/ui/omnibox/omnibox_view_controller.h
+++ b/ios/chrome/browser/ui/omnibox/omnibox_view_controller.h
@@ -12,27 +12,31 @@
 #import "ios/chrome/browser/ui/orchestrator/edit_view_animatee.h"
 #import "ios/chrome/browser/ui/orchestrator/location_bar_offset_provider.h"
 
-@protocol BrowserCommands;
-@protocol LoadQueryCommands;
-@protocol OmniboxCommands;
 @protocol OmniboxReturnDelegate;
 @class OmniboxViewController;
 class OmniboxTextChangeDelegate;
 
-@protocol OmniboxViewControllerDelegate
+// Delegate for text input changes in OmniboxViewController.
+@protocol OmniboxViewControllerTextInputDelegate
 
 // Called after the text input mode changes in the OmniboxViewController. This
 // means that the active keyboard has changed.
 - (void)omniboxViewControllerTextInputModeDidChange:
     (OmniboxViewController*)omniboxViewController;
 
-// Called after the user uses the "Visit copied link" context menu entry.
-- (void)omniboxViewControllerUserDidVisitCopiedLink:
-    (OmniboxViewController*)omniboxViewController;
+@end
 
-// Starts a reverse image search with `image`. Caller must check that the search
-// engine supports image search.
-- (void)omniboxViewControllerSearchImage:(UIImage*)image;
+// Delegate for paste actions in OmniboxViewController.
+@protocol OmniboxViewControllerPasteDelegate
+
+// User tapped on the keyboard accessory's paste button.
+- (void)didTapPasteToSearchButton:(NSArray<NSItemProvider*>*)itemProviders;
+// User tapped on the Search Copied Text from the omnibox menu.
+- (void)didTapSearchCopiedText;
+// User tapped on the Search Copied Image from the omnibox menu.
+- (void)didTapSearchCopiedImage;
+// User tapped on the Visit Copied Link from the omnibox menu.
+- (void)didTapVisitCopiedLink;
 
 @end
 
@@ -55,13 +59,10 @@
 @property(nonatomic, assign)
     UISemanticContentAttribute semanticContentAttribute;
 
-// The dispatcher for the paste and go action.
-@property(nonatomic, weak)
-    id<BrowserCommands, LoadQueryCommands, OmniboxCommands>
-        dispatcher;
-
 // The delegate for this object.
-@property(nonatomic, weak) id<OmniboxViewControllerDelegate> delegate;
+@property(nonatomic, weak) id<OmniboxViewControllerTextInputDelegate>
+    textInputDelegate;
+@property(nonatomic, weak) id<OmniboxViewControllerPasteDelegate> pasteDelegate;
 @property(nonatomic, weak) id<OmniboxReturnDelegate> returnKeyDelegate;
 
 // Designated initializer.
diff --git a/ios/chrome/browser/ui/omnibox/omnibox_view_controller.mm b/ios/chrome/browser/ui/omnibox/omnibox_view_controller.mm
index de400c9..417d329 100644
--- a/ios/chrome/browser/ui/omnibox/omnibox_view_controller.mm
+++ b/ios/chrome/browser/ui/omnibox/omnibox_view_controller.mm
@@ -11,9 +11,6 @@
 #include "components/omnibox/browser/omnibox_field_trial.h"
 #include "components/open_from_clipboard/clipboard_recent_content.h"
 #include "components/strings/grit/components_strings.h"
-#import "ios/chrome/browser/ui/commands/browser_commands.h"
-#import "ios/chrome/browser/ui/commands/load_query_commands.h"
-#import "ios/chrome/browser/ui/commands/omnibox_commands.h"
 #import "ios/chrome/browser/ui/default_promo/default_browser_utils.h"
 #import "ios/chrome/browser/ui/omnibox/omnibox_constants.h"
 #import "ios/chrome/browser/ui/omnibox/omnibox_container_view.h"
@@ -385,51 +382,7 @@
   // Interacted while focused.
   self.omniboxInteractedWhileFocused = YES;
 
-  __weak __typeof(self) weakSelf = self;
-  auto textCompletion =
-      ^(__kindof id<NSItemProviderReading> providedItem, NSError* error) {
-        LogLikelyInterestedDefaultBrowserUserActivity(DefaultPromoTypeGeneral);
-        dispatch_async(dispatch_get_main_queue(), ^{
-          NSString* text = static_cast<NSString*>(providedItem);
-          if (text) {
-            [weakSelf.dispatcher loadQuery:text immediately:YES];
-            [weakSelf.dispatcher cancelOmniboxEdit];
-          }
-        });
-      };
-  auto imageCompletion =
-      ^(__kindof id<NSItemProviderReading> providedItem, NSError* error) {
-        dispatch_async(dispatch_get_main_queue(), ^{
-          UIImage* image = static_cast<UIImage*>(providedItem);
-          if (image) {
-            [weakSelf.delegate omniboxViewControllerSearchImage:image];
-            [weakSelf.dispatcher cancelOmniboxEdit];
-          }
-        });
-      };
-  for (NSItemProvider* itemProvider in itemProviders) {
-    if (self.searchByImageEnabled &&
-        [itemProvider canLoadObjectOfClass:[UIImage class]]) {
-      RecordAction(
-          UserMetricsAction("Mobile.OmniboxPasteButton.SearchCopiedImage"));
-      [itemProvider loadObjectOfClass:[UIImage class]
-                    completionHandler:imageCompletion];
-      break;
-    } else if ([itemProvider canLoadObjectOfClass:[NSURL class]]) {
-      RecordAction(
-          UserMetricsAction("Mobile.OmniboxPasteButton.SearchCopiedLink"));
-      // Load URL as a NSString to avoid further conversion.
-      [itemProvider loadObjectOfClass:[NSString class]
-                    completionHandler:textCompletion];
-      break;
-    } else if ([itemProvider canLoadObjectOfClass:[NSString class]]) {
-      RecordAction(
-          UserMetricsAction("Mobile.OmniboxPasteButton.SearchCopiedText"));
-      [itemProvider loadObjectOfClass:[NSString class]
-                    completionHandler:textCompletion];
-      break;
-    }
-  }
+  [self.pasteDelegate didTapPasteToSearchButton:itemProviders];
 }
 
 #pragma mark - OmniboxConsumer
@@ -493,7 +446,7 @@
   [self.textField updateTextDirection];
   self.semanticContentAttribute = [self.textField bestSemanticContentAttribute];
 
-  [self.delegate omniboxViewControllerTextInputModeDidChange:self];
+  [self.textInputDelegate omniboxViewControllerTextInputModeDidChange:self];
 }
 
 - (void)updateCachedClipboardState {
@@ -659,36 +612,16 @@
   RecordAction(
       UserMetricsAction("Mobile.OmniboxContextMenu.SearchCopiedImage"));
   self.omniboxInteractedWhileFocused = YES;
-  __weak __typeof(self) weakSelf = self;
-  ClipboardRecentContent::GetInstance()->GetRecentImageFromClipboard(
-      base::BindOnce(^(absl::optional<gfx::Image> optionalImage) {
-        if (!optionalImage) {
-          return;
-        }
-        UIImage* image = optionalImage.value().ToUIImage();
-        [weakSelf.delegate omniboxViewControllerSearchImage:image];
-      }));
+  [self.pasteDelegate didTapSearchCopiedImage];
 }
 
 - (void)visitCopiedLink:(id)sender {
   // A search using clipboard link is activity that should indicate a user
   // that would be interested in setting Chrome as the default browser.
   LogLikelyInterestedDefaultBrowserUserActivity(DefaultPromoTypeGeneral);
-  [self.delegate omniboxViewControllerUserDidVisitCopiedLink:self];
   RecordAction(UserMetricsAction("Mobile.OmniboxContextMenu.VisitCopiedLink"));
   self.omniboxInteractedWhileFocused = YES;
-  __weak __typeof(self) weakSelf = self;
-  ClipboardRecentContent::GetInstance()->GetRecentURLFromClipboard(
-      base::BindOnce(^(absl::optional<GURL> optionalURL) {
-        if (!optionalURL) {
-          return;
-        }
-        NSString* url = base::SysUTF8ToNSString(optionalURL.value().spec());
-        dispatch_async(dispatch_get_main_queue(), ^{
-          [weakSelf.dispatcher loadQuery:url immediately:YES];
-          [weakSelf.dispatcher cancelOmniboxEdit];
-        });
-      }));
+  [self.pasteDelegate didTapVisitCopiedLink];
 }
 
 - (void)searchCopiedText:(id)sender {
@@ -697,18 +630,7 @@
   LogLikelyInterestedDefaultBrowserUserActivity(DefaultPromoTypeGeneral);
   RecordAction(UserMetricsAction("Mobile.OmniboxContextMenu.SearchCopiedText"));
   self.omniboxInteractedWhileFocused = YES;
-  __weak __typeof(self) weakSelf = self;
-  ClipboardRecentContent::GetInstance()->GetRecentTextFromClipboard(
-      base::BindOnce(^(absl::optional<std::u16string> optionalText) {
-        if (!optionalText) {
-          return;
-        }
-        NSString* query = base::SysUTF16ToNSString(optionalText.value());
-        dispatch_async(dispatch_get_main_queue(), ^{
-          [weakSelf.dispatcher loadQuery:query immediately:YES];
-          [weakSelf.dispatcher cancelOmniboxEdit];
-        });
-      }));
+  [self.pasteDelegate didTapSearchCopiedText];
 }
 
 #pragma mark - UIScribbleInteractionDelegate