910e62b5创建于 1月15日历史提交
// Copyright 2023 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "content/shell/browser/shell_web_contents_view_delegate.h"

#import <UIKit/UIKit.h>

#include <memory>

#include "base/apple/foundation_util.h"
#include "base/command_line.h"
#include "base/memory/weak_ptr.h"
#include "base/notimplemented.h"
#include "build/build_config.h"
#include "content/public/browser/context_menu_params.h"
#include "content/public/browser/navigation_controller.h"
#include "content/public/browser/render_widget_host_view.h"
#include "content/public/browser/web_contents.h"
#include "content/shell/browser/shell_web_contents_view_delegate_creator.h"
#include "content/shell/common/shell_switches.h"
#include "third_party/blink/public/common/context_menu_data/edit_flags.h"

enum {
  ShellContextMenuItemCutTag = 0,
  ShellContextMenuItemCopyTag,
  ShellContextMenuItemCopyLinkTag,
  ShellContextMenuItemPasteTag,
  ShellContextMenuItemDeleteTag,
  ShellContextMenuItemOpenLinkTag
};

// A hidden button used only for creating context menus. The only way to
// programmatically trigger a context menu on iOS is to trigger the primary
// action of a button that shows a context menu as its primary action.
@interface ContextMenuHiddenButton : UIButton

// The frame determines the position at which the context menu is shown.
+ (instancetype)buttonWithFrame:(CGRect)frame
              contextMenuParams:(content::ContextMenuParams)params
                 forWebContents:(content::WebContents*)webContents;
@end

@implementation ContextMenuHiddenButton {
  content::ContextMenuParams _params;
  base::WeakPtr<content::WebContents> _webContents;
}

+ (instancetype)buttonWithFrame:(CGRect)frame
              contextMenuParams:(content::ContextMenuParams)params
                 forWebContents:(content::WebContents*)webContents {
  ContextMenuHiddenButton* button =
      [ContextMenuHiddenButton buttonWithType:UIButtonTypeSystem];
  button.hidden = YES;
  button.userInteractionEnabled = NO;
  button.contextMenuInteractionEnabled = YES;
  button.showsMenuAsPrimaryAction = YES;
  button.frame = frame;
  button.layer.zPosition = CGFLOAT_MIN;
  button->_params = params;
  button->_webContents = webContents->GetWeakPtr();
  return button;
}

- (UIContextMenuConfiguration*)contextMenuInteraction:
                                   (UIContextMenuInteraction*)interaction
                       configurationForMenuAtLocation:(CGPoint)location {
  UIContextMenuConfiguration* config = [UIContextMenuConfiguration
      configurationWithIdentifier:nil
                  previewProvider:nil
                   actionProvider:^UIMenu* _Nullable(
                       NSArray<UIMenuElement*>* _Nonnull suggestedActions) {
                     return [self buildContextMenuItems];
                   }];
  [super contextMenuInteraction:interaction
      configurationForMenuAtLocation:location];
  return config;
}

- (void)contextMenuInteraction:(UIContextMenuInteraction*)interaction
       willEndForConfiguration:(UIContextMenuConfiguration*)configuration
                      animator:(id<UIContextMenuInteractionAnimating>)animator {
  [super contextMenuInteraction:interaction
        willEndForConfiguration:configuration
                       animator:animator];
  if (_webContents) {
    _webContents->NotifyContextMenuClosed(_params.link_followed,
                                          _params.impression);
  }
}

- (UIAction*)makeMenuItem:(NSString*)title menuTag:(NSInteger)tag {
  auto menuActionHandler = ^(UIAction* action) {
    switch (tag) {
      case ShellContextMenuItemCutTag:
        self->_webContents->Cut();
        break;
      case ShellContextMenuItemCopyTag:
        self->_webContents->Copy();
        break;
#if BUILDFLAG(IS_IOS_TVOS)
        TVOS_NOT_YET_IMPLEMENTED();
#else
      case ShellContextMenuItemCopyLinkTag: {
        UIPasteboard* pasteboard = [UIPasteboard generalPasteboard];
        pasteboard.string = [NSString
            stringWithUTF8String:self->_params.link_url.spec().c_str()];
        break;
      }
#endif
      case ShellContextMenuItemPasteTag:
        self->_webContents->Paste();
        break;
      case ShellContextMenuItemDeleteTag:
        self->_webContents->Delete();
        break;
      case ShellContextMenuItemOpenLinkTag: {
        content::NavigationController::LoadURLParams params(
            self->_params.link_url);
        self->_webContents->GetController().LoadURLWithParams(params);
        break;
      }
    }
  };

  UIAction* menu = [UIAction actionWithTitle:title
                                       image:nil
                                  identifier:nil
                                     handler:menuActionHandler];
  return menu;
}

- (UIMenu*)buildContextMenuItems {
  bool hasLink = !_params.unfiltered_link_url.is_empty();
  bool hasSelection = !_params.selection_text.empty();
  bool isEditable = _params.is_editable;

  NSMutableArray* menuItems = [[NSMutableArray alloc] init];
  if (hasLink) {
    [menuItems addObject:[self makeMenuItem:@"Go to the Link"
                                    menuTag:ShellContextMenuItemOpenLinkTag]];
#if BUILDFLAG(IS_IOS_TVOS)
    TVOS_NOT_YET_IMPLEMENTED();
#else
    [menuItems addObject:[self makeMenuItem:@"Copy Link"
                                    menuTag:ShellContextMenuItemCopyLinkTag]];
#endif
  }

  if (isEditable) {
    if (_params.edit_flags & blink::ContextMenuDataEditFlags::kCanCut) {
      [menuItems addObject:[self makeMenuItem:@"Cut"
                                      menuTag:ShellContextMenuItemCutTag]];
    }

    if (_params.edit_flags & blink::ContextMenuDataEditFlags::kCanCopy) {
      [menuItems addObject:[self makeMenuItem:@"Copy"
                                      menuTag:ShellContextMenuItemCopyTag]];
    }

    if (_params.edit_flags & blink::ContextMenuDataEditFlags::kCanPaste) {
      [menuItems addObject:[self makeMenuItem:@"Paste"
                                      menuTag:ShellContextMenuItemPasteTag]];
    }

    if (_params.edit_flags & blink::ContextMenuDataEditFlags::kCanDelete) {
      [menuItems addObject:[self makeMenuItem:@"Delete"
                                      menuTag:ShellContextMenuItemDeleteTag]];
    }
  } else if (hasSelection) {
    [menuItems addObject:[self makeMenuItem:@"Copy"
                                    menuTag:ShellContextMenuItemCopyTag]];
  }

  NSString* title =
      hasLink ? [NSString
                    stringWithUTF8String:self->_params.link_url.spec().c_str()]
              : @"";
  return [UIMenu menuWithTitle:title children:menuItems];
}

@end

namespace content {

namespace {

gfx::NativeView GetContentNativeView(WebContents* web_contents) {
  RenderWidgetHostView* rwhv = web_contents->GetRenderWidgetHostView();
  if (!rwhv) {
    return gfx::NativeView();
  }
  return rwhv->GetNativeView();
}

}  // namespace

class ShellWebContentsUIButtonHolder {
 public:
  UIButton* __strong button_;
};

std::unique_ptr<WebContentsViewDelegate> CreateShellWebContentsViewDelegate(
    WebContents* web_contents) {
  return std::make_unique<ShellWebContentsViewDelegate>(web_contents);
}

ShellWebContentsViewDelegate::ShellWebContentsViewDelegate(
    WebContents* web_contents)
    : web_contents_(web_contents) {
  DCHECK(web_contents_);  // Avoids 'unused private field' build error.
  hidden_button_ = std::make_unique<ShellWebContentsUIButtonHolder>();
}

ShellWebContentsViewDelegate::~ShellWebContentsViewDelegate() {}

void ShellWebContentsViewDelegate::ShowContextMenu(
    RenderFrameHost& render_frame_host,
    const ContextMenuParams& params) {
  if (switches::IsRunWebTestsSwitchPresent()) {
    return;
  }

  UIView* view = base::apple::ObjCCastStrict<UIView>(
      GetContentNativeView(web_contents_).Get());
  CGRect frame = CGRectMake(params.x, params.y, 0, 0);

  [hidden_button_->button_ removeFromSuperview];
  hidden_button_->button_ =
      [ContextMenuHiddenButton buttonWithFrame:frame
                             contextMenuParams:params
                                forWebContents:web_contents_];
  [view addSubview:hidden_button_->button_];
  [hidden_button_->button_ performPrimaryAction];
}

}  // namespace content