chromium/ios/chrome/browser/ui/menu/browser_action_factory.mm

// Copyright 2021 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#import "ios/chrome/browser/ui/menu/browser_action_factory.h"

#import "base/strings/sys_string_conversions.h"
#import "components/open_from_clipboard/clipboard_recent_content.h"
#import "components/prefs/pref_service.h"
#import "components/search_engines/template_url_service.h"
#import "ios/chrome/browser/incognito_reauth/ui_bundled/incognito_reauth_scene_agent.h"
#import "ios/chrome/browser/policy/model/policy_util.h"
#import "ios/chrome/browser/search_engines/model/template_url_service_factory.h"
#import "ios/chrome/browser/shared/model/prefs/pref_names.h"
#import "ios/chrome/browser/shared/model/profile/profile_ios.h"
#import "ios/chrome/browser/shared/public/commands/application_commands.h"
#import "ios/chrome/browser/shared/public/commands/browser_coordinator_commands.h"
#import "ios/chrome/browser/shared/public/commands/command_dispatcher.h"
#import "ios/chrome/browser/shared/public/commands/lens_commands.h"
#import "ios/chrome/browser/shared/public/commands/load_query_commands.h"
#import "ios/chrome/browser/shared/public/commands/open_lens_input_selection_command.h"
#import "ios/chrome/browser/shared/public/commands/open_new_tab_command.h"
#import "ios/chrome/browser/shared/public/commands/qr_scanner_commands.h"
#import "ios/chrome/browser/shared/public/commands/save_image_to_photos_command.h"
#import "ios/chrome/browser/shared/public/commands/save_to_photos_commands.h"
#import "ios/chrome/browser/shared/public/features/features.h"
#import "ios/chrome/browser/shared/ui/symbols/symbols.h"
#import "ios/chrome/browser/shared/ui/util/pasteboard_util.h"
#import "ios/chrome/browser/ui/menu/action_factory+protected.h"
#import "ios/chrome/browser/url_loading/model/image_search_param_generator.h"
#import "ios/chrome/browser/url_loading/model/url_loading_browser_agent.h"
#import "ios/chrome/browser/url_loading/model/url_loading_params.h"
#import "ios/chrome/grit/ios_strings.h"
#import "ui/base/l10n/l10n_util_mac.h"
#import "url/gurl.h"

@interface BrowserActionFactory ()

// Current browser instance.
@property(nonatomic, assign) Browser* browser;

@end

@implementation BrowserActionFactory

- (instancetype)initWithBrowser:(Browser*)browser
                       scenario:(MenuScenarioHistogram)scenario {
  DCHECK(browser);
  if ((self = [super initWithScenario:scenario])) {
    _browser = browser;
  }
  return self;
}

- (UIAction*)actionToOpenInNewTabWithURL:(const GURL)URL
                              completion:(ProceduralBlock)completion {
  UrlLoadParams params = UrlLoadParams::InNewTab(URL);
  UrlLoadingBrowserAgent* loadingAgent =
      UrlLoadingBrowserAgent::FromBrowser(self.browser);
  return [self actionToOpenInNewTabWithBlock:^{
    loadingAgent->Load(params);
    if (completion) {
      completion();
    }
  }];
}

- (UIAction*)actionToOpenInNewIncognitoTabWithURL:(const GURL)URL
                                       completion:(ProceduralBlock)completion {
  if (!_browser)
    return nil;

  UrlLoadParams params = UrlLoadParams::InNewTab(URL);
  params.in_incognito = YES;
  UrlLoadingBrowserAgent* loadingAgent =
      UrlLoadingBrowserAgent::FromBrowser(self.browser);
  return [self actionToOpenInNewIncognitoTabWithBlock:^{
    loadingAgent->Load(params);
    if (completion) {
      completion();
    }
  }];
}

- (UIAction*)actionToOpenInNewIncognitoTabWithBlock:(ProceduralBlock)block {
  // Wrap the block with the incognito auth check, if necessary.
  IncognitoReauthSceneAgent* reauthAgent =
      [IncognitoReauthSceneAgent agentFromScene:self.browser->GetSceneState()];
  if (reauthAgent.authenticationRequired) {
    block = ^{
      [reauthAgent
          authenticateIncognitoContentWithCompletionBlock:^(BOOL success) {
            if (success && block != nullptr) {
              block();
            }
          }];
    };
  }

  UIImage* image =
      CustomSymbolWithPointSize(kIncognitoSymbol, kSymbolActionPointSize);
  ProceduralBlock completionBlock =
      [self recordMobileWebContextMenuOpenTabActionWithBlock:block];

  return [self actionWithTitle:l10n_util::GetNSString(
                                   IDS_IOS_OPEN_IN_INCOGNITO_ACTION_TITLE)
                         image:image
                          type:MenuActionType::OpenInNewIncognitoTab
                         block:completionBlock];
}

- (UIAction*)actionToOpenInNewWindowWithURL:(const GURL)URL
                             activityOrigin:
                                 (WindowActivityOrigin)activityOrigin {
  id<ApplicationCommands> windowOpener = HandlerForProtocol(
      self.browser->GetCommandDispatcher(), ApplicationCommands);

  UIImage* image = DefaultSymbolWithPointSize(kNewWindowActionSymbol,
                                              kSymbolActionPointSize);
  NSUserActivity* activity = ActivityToLoadURL(activityOrigin, URL);
  return [self actionWithTitle:l10n_util::GetNSString(
                                   IDS_IOS_CONTENT_CONTEXT_OPENINNEWWINDOW)
                         image:image
                          type:MenuActionType::OpenInNewWindow
                         block:^{
                           [windowOpener openNewWindowWithActivity:activity];
                         }];
}

- (UIAction*)actionToOpenInNewWindowWithActivity:(NSUserActivity*)activity {
  id<ApplicationCommands> windowOpener = HandlerForProtocol(
      self.browser->GetCommandDispatcher(), ApplicationCommands);

  UIImage* image = DefaultSymbolWithPointSize(kNewWindowActionSymbol,
                                              kSymbolActionPointSize);
  return [self actionWithTitle:l10n_util::GetNSString(
                                   IDS_IOS_CONTENT_CONTEXT_OPENINNEWWINDOW)
                         image:image
                          type:MenuActionType::OpenInNewWindow
                         block:^{
                           [windowOpener openNewWindowWithActivity:activity];
                         }];
}

- (UIAction*)actionOpenImageWithURL:(const GURL)URL
                         completion:(ProceduralBlock)completion {
  UrlLoadingBrowserAgent* loadingAgent =
      UrlLoadingBrowserAgent::FromBrowser(self.browser);
  UIImage* image = DefaultSymbolWithPointSize(kOpenImageActionSymbol,
                                              kSymbolActionPointSize);
  UIAction* action = [self
      actionWithTitle:l10n_util::GetNSString(IDS_IOS_CONTENT_CONTEXT_OPENIMAGE)
                image:image
                 type:MenuActionType::OpenImageInCurrentTab
                block:^{
                  loadingAgent->Load(UrlLoadParams::InCurrentTab(URL));
                  if (completion) {
                    completion();
                  }
                }];
  return action;
}

- (UIAction*)actionOpenImageInNewTabWithUrlLoadParams:(UrlLoadParams)params
                                           completion:
                                               (ProceduralBlock)completion {
  UrlLoadingBrowserAgent* loadingAgent =
      UrlLoadingBrowserAgent::FromBrowser(self.browser);
  UIImage* image =
      CustomSymbolWithPointSize(kPhotoBadgePlusSymbol, kSymbolActionPointSize);
  UIAction* action =
      [self actionWithTitle:l10n_util::GetNSString(
                                IDS_IOS_CONTENT_CONTEXT_OPENIMAGENEWTAB)
                      image:image
                       type:MenuActionType::OpenImageInNewTab
                      block:^{
                        loadingAgent->Load(params);
                        if (completion) {
                          completion();
                        }
                      }];
  return action;
}

- (UIAction*)actionToOpenNewTab {
  id<ApplicationCommands> handler = HandlerForProtocol(
      self.browser->GetCommandDispatcher(), ApplicationCommands);
  UIAction* action =
      [self actionWithTitle:l10n_util::GetNSString(IDS_IOS_TOOLS_MENU_NEW_TAB)
                      image:DefaultSymbolWithPointSize(kNewTabActionSymbol,
                                                       kSymbolActionPointSize)
                       type:MenuActionType::OpenNewTab
                      block:^{
                        [handler openURLInNewTab:[OpenNewTabCommand
                                                     commandWithIncognito:NO]];
                      }];
  if (IsIncognitoModeForced(self.browser->GetBrowserState()->GetPrefs())) {
    action.attributes = UIMenuElementAttributesDisabled;
  }
  return action;
}

- (UIAction*)actionToOpenNewIncognitoTab {
  id<ApplicationCommands> handler = HandlerForProtocol(
      self.browser->GetCommandDispatcher(), ApplicationCommands);
  UIAction* action =
      [self actionWithTitle:l10n_util::GetNSString(
                                IDS_IOS_TOOLS_MENU_NEW_INCOGNITO_TAB)
                      image:CustomSymbolWithPointSize(kIncognitoSymbol,
                                                      kSymbolActionPointSize)
                       type:MenuActionType::OpenNewIncognitoTab
                      block:^{
                        [handler openURLInNewTab:[OpenNewTabCommand
                                                     commandWithIncognito:YES]];
                      }];
  if (IsIncognitoModeDisabled(self.browser->GetBrowserState()->GetPrefs())) {
    action.attributes = UIMenuElementAttributesDisabled;
  }
  return action;
}

- (UIAction*)actionToCloseCurrentTab {
  __weak id<BrowserCoordinatorCommands> handler = HandlerForProtocol(
      self.browser->GetCommandDispatcher(), BrowserCoordinatorCommands);
  UIAction* action =
      [self actionWithTitle:l10n_util::GetNSString(IDS_IOS_TOOLS_MENU_CLOSE_TAB)
                      image:DefaultSymbolWithPointSize(kXMarkSymbol,
                                                       kSymbolActionPointSize)
                       type:MenuActionType::CloseCurrentTabs
                      block:^{
                        [handler closeCurrentTab];
                      }];
  action.attributes = UIMenuElementAttributesDestructive;
  return action;
}

- (UIAction*)actionToShowQRScanner {
  id<QRScannerCommands> handler = HandlerForProtocol(
      self.browser->GetCommandDispatcher(), QRScannerCommands);
  return [self
      actionWithTitle:l10n_util::GetNSString(IDS_IOS_TOOLS_MENU_QR_SCANNER)
                image:DefaultSymbolWithPointSize(kQRCodeFinderActionSymbol,
                                                 kSymbolActionPointSize)
                 type:MenuActionType::ShowQRScanner
                block:^{
                  [handler showQRScanner];
                }];
}

- (UIAction*)actionToSearchWithLensWithEntryPoint:(LensEntrypoint)entryPoint {
  id<LensCommands> handler =
      HandlerForProtocol(self.browser->GetCommandDispatcher(), LensCommands);
  return
      [self actionWithTitle:l10n_util::GetNSString(
                                IDS_IOS_TOOLS_MENU_LENS_CAMERA_SEARCH)
                      image:CustomSymbolWithPointSize(kCameraLensSymbol,
                                                      kSymbolActionPointSize)
                       type:MenuActionType::LensCameraSearch
                      block:^{
                        OpenLensInputSelectionCommand* command =
                            [[OpenLensInputSelectionCommand alloc]
                                    initWithEntryPoint:entryPoint
                                     presentationStyle:
                                         LensInputSelectionPresentationStyle::
                                             SlideFromRight
                                presentationCompletion:nil];
                        [handler openLensInputSelection:command];
                      }];
}

- (UIAction*)actionToSaveToPhotosWithImageURL:(const GURL&)imageURL
                                     referrer:(const web::Referrer&)referrer
                                     webState:(web::WebState*)webState
                                        block:(ProceduralBlock)block {
  __weak id<SaveToPhotosCommands> handler = HandlerForProtocol(
      self.browser->GetCommandDispatcher(), SaveToPhotosCommands);
  SaveImageToPhotosCommand* command =
      [[SaveImageToPhotosCommand alloc] initWithImageURL:imageURL
                                                referrer:referrer
                                                webState:webState];

#if BUILDFLAG(IOS_USE_BRANDED_SYMBOLS)
  UIImage* image =
      CustomSymbolWithPointSize(kGooglePhotosSymbol, kSymbolActionPointSize);
#else
  UIImage* image = DefaultSymbolWithPointSize(kSaveImageActionSymbol,
                                              kSymbolActionPointSize);
#endif

  return [self actionWithTitle:l10n_util::GetNSString(
                                   IDS_IOS_TOOLS_MENU_SAVE_IMAGE_TO_PHOTOS)
                         image:image
                          type:MenuActionType::SaveImageToGooglePhotos
                         block:^{
                           if (block) {
                             block();
                           }
                           [handler saveImageToPhotos:command];
                         }];
}

- (UIAction*)actionToStartVoiceSearch {
  id<ApplicationCommands> handler = HandlerForProtocol(
      self.browser->GetCommandDispatcher(), ApplicationCommands);
  return [self
      actionWithTitle:l10n_util::GetNSString(IDS_IOS_TOOLS_MENU_VOICE_SEARCH)
                image:DefaultSymbolWithPointSize(kMicrophoneSymbol,
                                                 kSymbolActionPointSize)
                 type:MenuActionType::StartVoiceSearch
                block:^{
                  [handler startVoiceSearch];
                }];
}

- (UIAction*)actionToStartNewSearch {
  id<ApplicationCommands> handler = HandlerForProtocol(
      self.browser->GetCommandDispatcher(), ApplicationCommands);
  UIAction* action = [self
      actionWithTitle:l10n_util::GetNSString(IDS_IOS_TOOLS_MENU_NEW_SEARCH)
                image:DefaultSymbolWithPointSize(kSearchSymbol,
                                                 kSymbolActionPointSize)
                 type:MenuActionType::StartNewSearch
                block:^{
                  OpenNewTabCommand* command =
                      [OpenNewTabCommand commandWithIncognito:NO];
                  command.shouldFocusOmnibox = YES;
                  [UIView performWithoutAnimation:^{
                    [handler openURLInNewTab:command];
                  }];
                }];

  if (IsIncognitoModeForced(self.browser->GetBrowserState()->GetPrefs())) {
    action.attributes = UIMenuElementAttributesDisabled;
  }

  return action;
}

- (UIAction*)actionToStartNewIncognitoSearch {
  id<ApplicationCommands> handler = HandlerForProtocol(
      self.browser->GetCommandDispatcher(), ApplicationCommands);
  UIAction* action =
      [self actionWithTitle:l10n_util::GetNSString(
                                IDS_IOS_TOOLS_MENU_NEW_INCOGNITO_SEARCH)
                      image:CustomSymbolWithPointSize(kIncognitoSymbol,
                                                      kSymbolActionPointSize)
                       type:MenuActionType::StartNewIncognitoSearch
                      block:^{
                        OpenNewTabCommand* command =
                            [OpenNewTabCommand commandWithIncognito:YES];
                        command.shouldFocusOmnibox = YES;
                        [UIView performWithoutAnimation:^{
                          [handler openURLInNewTab:command];
                        }];
                      }];

  if (IsIncognitoModeDisabled(self.browser->GetBrowserState()->GetPrefs())) {
    action.attributes = UIMenuElementAttributesDisabled;
  }

  return action;
}

- (UIAction*)actionToSearchCopiedImage {
  __weak __typeof(self) weakSelf = self;

  void (^clipboardAction)(std::optional<gfx::Image>) =
      ^(std::optional<gfx::Image> optionalImage) {
        if (!optionalImage || !weakSelf) {
          return;
        }
        __typeof(weakSelf) strongSelf = weakSelf;

        TemplateURLService* templateURLService =
            ios::TemplateURLServiceFactory::GetForBrowserState(
                strongSelf.browser->GetBrowserState());

        UIImage* image = [optionalImage.value().ToUIImage() copy];

        web::NavigationManager::WebLoadParams webParams =
            ImageSearchParamGenerator::LoadParamsForImage(image,
                                                          templateURLService);
        UrlLoadParams params = UrlLoadParams::InCurrentTab(webParams);

        UrlLoadingBrowserAgent::FromBrowser(strongSelf.browser)->Load(params);
      };

  return [self actionWithTitle:l10n_util::GetNSString(
                                   IDS_IOS_TOOLS_MENU_SEARCH_COPIED_IMAGE)
                         image:DefaultSymbolWithPointSize(
                                   kClipboardActionSymbol,
                                   kSymbolActionPointSize)
                          type:MenuActionType::SearchCopiedImage
                         block:^{
                           ClipboardRecentContent::GetInstance()
                               ->GetRecentImageFromClipboard(
                                   base::BindOnce(clipboardAction));
                         }];
}

- (UIAction*)actionToSearchCopiedURL {
  id<LoadQueryCommands> handler = HandlerForProtocol(
      self.browser->GetCommandDispatcher(), LoadQueryCommands);

  void (^clipboardAction)(std::optional<GURL>) =
      ^(std::optional<GURL> optionalURL) {
        if (!optionalURL) {
          return;
        }
        NSString* URL = base::SysUTF8ToNSString(optionalURL.value().spec());
        dispatch_async(dispatch_get_main_queue(), ^{
          [handler loadQuery:URL immediately:YES];
        });
      };

  return [self actionWithTitle:l10n_util::GetNSString(
                                   IDS_IOS_TOOLS_MENU_VISIT_COPIED_LINK)
                         image:DefaultSymbolWithPointSize(
                                   kClipboardActionSymbol,
                                   kSymbolActionPointSize)
                          type:MenuActionType::VisitCopiedLink
                         block:^{
                           ClipboardRecentContent::GetInstance()
                               ->GetRecentURLFromClipboard(
                                   base::BindOnce(clipboardAction));
                         }];
}

- (UIAction*)actionToSearchCopiedText {
  id<LoadQueryCommands> handler = HandlerForProtocol(
      self.browser->GetCommandDispatcher(), LoadQueryCommands);

  void (^clipboardAction)(std::optional<std::u16string>) =
      ^(std::optional<std::u16string> optionalText) {
        if (!optionalText) {
          return;
        }
        NSString* query = base::SysUTF16ToNSString(optionalText.value());
        dispatch_async(dispatch_get_main_queue(), ^{
          [handler loadQuery:query immediately:YES];
        });
      };

  return [self actionWithTitle:l10n_util::GetNSString(
                                   IDS_IOS_TOOLS_MENU_SEARCH_COPIED_TEXT)
                         image:DefaultSymbolWithPointSize(
                                   kClipboardActionSymbol,
                                   kSymbolActionPointSize)
                          type:MenuActionType::SearchCopiedText
                         block:^{
                           ClipboardRecentContent::GetInstance()
                               ->GetRecentTextFromClipboard(
                                   base::BindOnce(clipboardAction));
                         }];
}

@end