Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can the context menu in WKWebView on the Mac be modified or overridden?

I'm using a WKWebView in a Mac OS X application. I want to override the contextual menu that appears when the user Control + clicks or right clicks in the WKWebView, but I cannot find a way to accomplish this.

It should be noted that the context menu changes depending on the state of the WKWebView and what element is under the mouse when the context menu is invoked. For example, the context menu only has a single "Reload" item when the mouse is over an "empty" part of the content, whereas right clicking a link presents the options "Open Link", "Open Link In New Window", and so on. It would be helpful to have granular control over these different menus if possible.

The older WebUIDelegate provides the - webView:contextMenuItemsForElement:defaultMenuItems: method that allows you to customize the context menu for WebView instances; I'm essentially looking for the analog to this method for WKWebView, or any way to duplicate the functionality.

like image 904
Justin Michael Avatar asked Mar 02 '15 00:03

Justin Michael


3 Answers

You can do this by intercepting the contextmenu event in your javascript, reporting the event back to your OSX container through a scriptMessageHandler, then popping up a menu from OSX. You can pass context back through the body field of the script message to show an appropriate menu, or use a different handler for each one.

Setting up callback handler in 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 code using jquery:

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

Responding to it from Objective 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);
}
like image 66
Ken Cooper Avatar answered Oct 16 '22 22:10

Ken Cooper


You can intercept context menu items of the WKWebView class by subclassing it and implementing the willOpenMenu method like this:

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")
        }
    }
}

Instead of this you can also simply hide the menu items with menuItem.isHidden = true

Detecting the chosen menu item is one thing, but knowing what the user actually clicked in the WKWebView control is the next challenge :)

It's also possible to add new menu items to the menu.items array.

like image 44
Ely Avatar answered Oct 17 '22 00:10

Ely


Objective C solution. The best solution is to subclass WKWebView and intercept mouse clicks. It works great.

@implementation WKReportWebView

// Ctrl+click seems to send this not rightMouse
-(void)mouseDown:(NSEvent *)event
{
    if(event.modifierFlags & NSEventModifierFlagControl)
        return [self rightMouseDown:event];

    [super mouseDown:event]; // Catch scrollbar mouse events
}

-(void)rightMouseDown:(NSEvent *)theEvent
{
    NSMenu *rightClickMenu = [[NSMenu alloc] initWithTitle:@"Print Menu"];
    [rightClickMenu insertItemWithTitle:NSLocalizedString(@"Print", nil) action:@selector(print:) keyEquivalent:@"" atIndex:0];
    [NSMenu popUpContextMenu:rightClickMenu withEvent:theEvent forView:self];
}
@end
like image 36
Cliff Ribaudo Avatar answered Oct 17 '22 00:10

Cliff Ribaudo