Как можно изменить или переопределить контекстное меню в WKWebView на Mac?

Я использую WKWebView в приложении Mac OS X. Я хочу переопределить контекстное меню, которое появляется, когда пользователь Control + нажимает или щелкает правой WKWebView в WKWebView, но я не могу найти способ выполнить это.

Следует отметить, что контекстное меню изменяется в зависимости от состояния WKWebView и того, что элемент находится под мышью при вызове контекстного меню. Например, контекстное меню имеет только один элемент "Перезагрузка", когда мышь находится над "пустой" частью содержимого, тогда как при щелчке правой кнопкой мыши на ссылке представлены опции "Открыть ссылку", "Открыть ссылку в новом окне" и скоро. Было бы полезно иметь гранулированный контроль над этими различными меню, если это возможно.

Более старый WebUIDelegate предоставляет метод - webView:contextMenuItemsForElement:defaultMenuItems: метод, который позволяет настраивать контекстное меню для экземпляров WebView; Я по существу ищу аналог этого метода для WKWebView или любой способ дублировать функциональность.

Ответ 1

Вы можете сделать это, перехватив событие contextmenu в своем javascript, сообщая о событии обратно в контейнер OSX через scriptMessageHandler, а затем вытаскивая меню из OSX. Вы можете передать контекст обратно через поле тела сообщения сценария, чтобы отобразить соответствующее меню или использовать другой обработчик для каждого из них.

Настройка обработчика обратного вызова в Objective C:

WKUserContentController *contentController = [[WKUserContentController alloc]init];
[contentController addScriptMessageHandler:self name:@"callbackHandler"];
config.userContentController = contentController;
self.mainWebView = [[WKWebView alloc] initWithFrame:self.view.frame configuration:config];

Javascript код с помощью jquery:

$(nodeId).on("contextmenu", function (evt) {
   window.webkit.messageHandlers.callbackHandler.postMessage({body: "..."});
   evt.preventDefault();
});

Отвечая на него из задачи C:

-(void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message
{
    if ([message.name isEqualToString:@"callbackHandler"]) {
      [self popupMenu:message.body];
    }
}

-(void)popupMenu:(NSString *)context {
    NSMenu *theMenu = [[NSMenu alloc] initWithTitle:@"Context Menu"];
    [theMenu insertItemWithTitle:@"Beep" action:@selector(beep:) keyEquivalent:@"" atIndex:0];
    [theMenu insertItemWithTitle:@"Honk" action:@selector(honk:) keyEquivalent:@"" atIndex:1];
    [theMenu popUpMenuPositioningItem:theMenu.itemArray[0] atLocation:NSPointFromCGPoint(CGPointMake(0,0)) inView:self.view];
}

-(void)beep:(id)val {
    NSLog(@"got beep %@", val);
}

-(void)honk:(id)val {
    NSLog(@"got honk %@", val);
}

Ответ 2

Вы можете перехватывать элементы контекстного меню класса WKWebView, подклассифицируя его и внедряя метод willOpenMenu следующим образом:

class MyWebView: WKWebView {

    override func willOpenMenu(_ menu: NSMenu, with event: NSEvent) {
        for menuItem in menu.items {
            if  menuItem.identifier?.rawValue == "WKMenuItemIdentifierDownloadImage" ||
                menuItem.identifier?.rawValue == "WKMenuItemIdentifierDownloadLinkedFile" {
                menuItem.action = #selector(menuClick(_:))
                menuItem.target = self
            }
        }
    }

    @objc func menuClick(_ sender: AnyObject) {
        if let menuItem = sender as? NSMenuItem {
            Swift.print("Menu \(menuItem.title) clicked")
        }
    }
}

Вместо этого вы также можете просто скрыть пункты меню с помощью menuItem.isHidden = true

Обнаружение выбранного пункта меню - это одно, но знание того, что пользователь на самом деле нажал в элементе управления WKWebView, является следующей задачей:

Также возможно добавить новые пункты меню в массив menu.items.