Manipulate paste content in WKWebView
Asked Answered
B

1

1

I need to manipulate the text that is pasted into a WKWebView (from any source) running an asynchronous operation that can take some time.

My original idea was to use Javascript and the WKWebView configuration in order to get the onpaste event:

WKUserContentController *wkUController = [[WKUserContentController alloc] init];

NSString *pasteJSSource = @"document.addEventListener('onpaste', function(){ window.webkit.messageHandlers.ComposerListener.postMessage('onpaste happened!'); })";

WKUserScript *pasteScript = [[WKUserScript alloc] initWithSource:pasteJSSource injectionTime:WKUserScriptInjectionTimeAtDocumentEnd forMainFrameOnly: NO];

[wkUController addScriptMessageHandler:self name:@"ComposerListener"];

[wkUController addUserScript:pasteScript];

webViewConfiguration.userContentController = wkUController;

Then my class implements WKScriptMessageHandler

#pragma mark - WKScriptMessageHandler

- (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message
{
    NSLog(@"message: %@", message.body);
}

(Ignore Obj-c, swift is ok too)

But I have two problems:

  1. userContentController:didReceiveScriptMessage: is never called
  2. I don't know how to intercept the pasted code and replace it with something different

Any idea on how to solve this (even without JS that I don't obviously know :P )? Thanks.

Bidarka answered 9/4, 2020 at 14:53 Comment(0)
B
3

FOR POSTERITY:

I'm sure there a way to do this using JS exists and is cleaner but I managed to achieve the result using method swizzling:

//Method Swizzling
UIView *webContentView = self.webView.contentView;
if(webContentView != nil)
{
    //Paste:
    NSError *error;
    [webContentView swizzleMethod:@selector(paste:) withSelector:@selector(my_paste:) error:&error];
    if(error != nil)
    {
        NSLog("Failed to swizzle 'paste:' into WKContentView: %@, error);
        NSAssert(false, error);
    }

    ...
}

Where the contentView is:

- (UIView *)contentView
{
    return [self subviewWithClassName:@"WKContentView"];
}

The method my_paste: need to be part of UIResponder (that is implemented by the private WKContentView)

#pragma mark - Method Swizzling UIResponder

@interface UIResponder (WebComposerSwizzling)

- (void)my_paste:(id)sender;
#define original_paste my_paste

@end

@implementation UIResponder (WebComposerSwizzling)

- (void)my_paste:(id)sender
{
    MailComposerViewController* strongComposer = sCurrentComposer;
    if (strongComposer)
        [strongComposer manipulatePasteboard:nil];
    [self original_paste:sender];
}

@end

Note that sCurrentComposer is a static variable in my ViewController

__weak MailComposerViewController* sCurrentComposer;

The various utilities:

UIView+SubviewSearch

import UIKit

extension UIView {

    /// Find a subview corresponding to the className parameter, recursively.
    @objc public func subviewWithClassName(_ className: String) -> UIView? {
        if NSStringFromClass(type(of: self)) == className {
            return self
        } else {
            for subview in subviews {
                return subview.subviewWithClassName(className)
            }
        }
        return nil
    }
}

NSObject+Swizzling

import Foundation

extension NSObject {

    enum NSObjectSwizzlingError: Error {
        case originalSelectorNotFound
    }

    @objc public func swizzleMethod(_ currentSelector: Selector, withSelector newSelector: Selector) throws {
        if let currentMethod = self.instanceMethod(for: currentSelector),
            let newMethod = self.instanceMethod(for:newSelector) {
            method_exchangeImplementations(currentMethod, newMethod)
        } else {
            throw NSObjectSwizzlingError.originalSelectorNotFound
        }
    }

    @objc public func instanceMethod(for selector: Selector) -> Method? {
        let classType: AnyClass! = object_getClass(self)
        return class_getInstanceMethod(classType, selector)
    }
}

(I'm sorry about the Swift <> OBJ-C mix)

Bidarka answered 17/4, 2020 at 15:15 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.