[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