How do I repurpose JSContext in UIWebView to WKScriptMessage for WKWebview - javascript

I want to convert a UIWebView library to use WkWebview. The remaining piece is switching out JSContext because the valueForKeyPath doesn't work anymore. So how do I rewrite something like the following to use WKScriptMessage as the other SO link suggests? (swift or ObjC answer is fine) How to get JSContext from WKWebView
JSContext *ctx = [webView valueForKeyPath:#"documentView.webView.mainFrame.javaScriptContext"];
ctx[#"contentPasteCallback"] = ^(JSValue *msg) {
__weak typeof(weakSelf) StrongSelf = weakSelf;
StrongSelf.editorPaste = YES;
};
[ctx evaluateScript:#"document.getElementById('zss_editor_content').addEventListener('paste', contentPasteCallback, false);"];

I have converted UIWebView to WKWebView for Editor. I have created fork from this Github Link. The link to my demo can be found here.

Ok I figured it out. See the PR https://github.com/nnhubbard/ZSSRichTextEditor/pull/243
Basically you inject javascript to start the listeners. The key here is to pass the function which calls webkit using postMessage and use the same name, in my case 'jsm' as what was setup when you create the WKUserContentController object
NSString *pasteListener = #"document.getElementById('zss_editor_content').addEventListener('paste', function() {window.webkit.messageHandlers.jsm.postMessage('paste');});";
[self.editorView evaluateJavaScript:pasteListener completionHandler:^(NSString *result, NSError *error) {
if (error != NULL) {
NSLog(#"%#", error);
}
}];
and then you listen for the response in the userContentController: didReceiveScript delegate method from WKScriptMessageHandler
- (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message {
NSString *messageString = (NSString *)message.body;
if ([messageString isEqualToString:#"paste"]) {
self.editorPaste = YES;
}

Related

WKWebView evaluateJavaScript is not working for youtube embedded video url

I've loaded a youtube embedded video to WKWebView:
https://www.youtube.com/embed/amtuB-2wGeQ?playsinline=1&autoplay=1
Then after WKWebView didFinishNavigation event is fired, I call:
func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
webView.evaluateJavaScript("document.querySelector('video').play()", completionHandler: { (result, error) in
if let r = result {
print(r)
}
if let e = error {
print(e)
}
})
}
However, the javascript command is not being executed, both error and result are nil.
When I execute the same javascript command in Chrome developer tool, it successfully played the video and paused the video by calling "play()" and "pause()".
document.querySelector('video').play()
document.querySelector('video').pause()
I don't know what happened inside WKWebView, any ideas?
Thanks!
OK, finally figured out why it is not working.
If you are also struggling with "why my javascript is not working in WKWebView", below is a neat way to figure out why:
1. Open Safari on your desktop, Preferences -> Advanced, enable "show developer menu in menu bar"
2. In the Safari menu, Develop -> simulator, connect your iPhone simulator
3. Now you can see the web page html and script in Safari, in the console input area, try run different javascript to see which one works.
4. Then call the one that works from "evaluateJavaScript"
The reason why it didn't work from my case is that the embedded web page was rendered differently between common web browser and WKWebView. I took the browser script as example which didn't work.
With the help of Safari debugger, you can see what's really going on inside WKWebView, which is really cool.
Hope it helps if you ever run into the same problem.
Thanks!
Use UIWebView instead of WKWebView and use this code for play youtube video.
#pragma mark - Embed Video
- (UIWebView *)embedVideoYoutubeWithURL:(NSString *)urlString andFrame:(CGRect)frame {
NSString *videoID = [self extractYoutubeVideoID:urlString];
NSString *embedHTML = #"\
<html><head>\
<style type=\"text/css\">\
body {\
background-color: transparent;\
color: white;\
}\
</style>\
</head><body style=\"margin:0\">\
<embed id=\"yt\" src=\"http://www.youtube.com/v/%#\" type=\"application/x-shockwave-flash\" \
width=\"%0.0f\" height=\"%0.0f\"></embed>\
</body></html>";
NSString *html = [NSString stringWithFormat:embedHTML, videoID, frame.size.width, frame.size.height];
UIWebView *videoWebView = [[UIWebView alloc] initWithFrame:frame];
[videoWebView loadHTMLString:html baseURL:nil];
return videoWebView;
}
/**
#see https://devforums.apple.com/message/705665#705665
extractYoutubeVideoID: works for the following URL formats:
www.youtube.com/v/VIDEOID
www.youtube.com?v=VIDEOID
www.youtube.com/watch?v=WHsHKzYOV2E&feature=youtu.be
www.youtube.com/watch?v=WHsHKzYOV2E
youtu.be/KFPtWedl7wg_U923
www.youtube.com/watch?feature=player_detailpage&v=WHsHKzYOV2E#t=31s
youtube.googleapis.com/v/WHsHKzYOV2E
*/
- (NSString *)extractYoutubeVideoID:(NSString *)urlYoutube {
NSString *regexString = #"(?<=v(=|/))([-a-zA-Z0-9_]+)|(?<=youtu.be/)([-a-zA-Z0-9_]+)";
NSError *error = NULL;
NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:regexString options:NSRegularExpressionCaseInsensitive error:&error];
NSRange rangeOfFirstMatch = [regex rangeOfFirstMatchInString:urlYoutube options:0 range:NSMakeRange(0, [urlYoutube length])];
if(!NSEqualRanges(rangeOfFirstMatch, NSMakeRange(NSNotFound, 0))) {
NSString *substringForFirstMatch = [urlYoutube substringWithRange:rangeOfFirstMatch];
return substringForFirstMatch;
}
return nil;
}
or use google's youtube-ios-player-helper pod

WKWebview's javascript call returns nil

I'm trying to migrate from using a UIWebview to a WKWebview, and I need to invoke various Javascript functions that live in the webview.
In the UIWebview I have no trouble doing this, but the WKWebview throws an WKErrorDomain Error 4 for any and all calls. I thought it might be a race condition where I was calling functions that didn't exist in the DOM yet, but I added a debug statement to make those same calls once the page loaded and they're not working. I even did a 'document.body.innerHTML' which returned nil when there's clearly content being displayed in the webview.
The twist is that when inspecting the web code via Safari Web Inspector, everything works well and all calls work.
Why and how could this be? I do initialize the WKWebView with a WKWebViewConfiguration so that it'll share cookies across different webviews, so there might be something there, but I'm stumped.
This is how i initialize the WKWebView
WKWebViewConfiguration* config = [[WKWebViewConfiguration alloc] init];
config.processPool = [[WKProcessPool alloc] init];
WKUserContentController* userContentController = WKUserContentController.new;
WKUserScript * cookieScript = [[WKUserScript alloc] initWithSource: #"document.cookie = 'cookie1=value1; domain=blah; path=/;';" injectionTime:WKUserScriptInjectionTimeAtDocumentStart forMainFrameOnly:NO];
[userContentController addUserScript:cookieScript];
config.userContentController = userContentController;
WKWebView *webView = [[WKWebView alloc] initWithFrame:CGRectMake(0,0,50,50) configuration:config];
Here's a debugging statement where I log the document's innerHTML into the console log, which prints (nil) instead of the HTML.
NSLog(#"%#", [self.webView evaluateJavaScript:#"document.body.innerHTML" completionHandler:nil]);
WKWebView executes JS async unlike UIWebView; You cannot print like how you are doing..
Try this
[self.webView evaluateJavaScript:#"document.body.innerHTML" completionHandler:^(id result, NSError *error) {
NSLog(#"Error is %#",error);
NSLog(#"JS result %#" ,result);
}];

Load JS Function stringByEvaluatingJavaScriptFromString with IOS

I have a simple javascript function with two variables for the geolocation. I need to pass two variables that I already have in my iOS application to this javascript function.
When I send the code via console in the browser everything is OK:
geoCallBackSuccess({coords:{'longitude':9.598470,'latitude':50.903697}})
Is my code correct?
In the simulator I see no effect...
NSString * param = #"{coords:{'longitude':9.598470,'latitude':50.903697}}";
NSString * jsCallBack = [NSString stringWithFormat:#"geoCallBackSuccess(%#)",param];
NSLog(#"viewDidLoad - jsCallBack: %#", jsCallBack);
[self.offerUrl stringByEvaluatingJavaScriptFromString:jsCallBack];
Log:
viewDidLoad - jsCallBack:
geoCallBackSuccess({coords:{'longitude':9.598470,'latitude':50.903697}})
For example, this works for me:
[self.offerUrl stringByEvaluatingJavaScriptFromString:#"document.getElementById(\"anzeige\").innerHTML=\"Hello World\";"];
I want to do the same as I do in Android:
#java...
public void getLocation() {
// magic
if (locationOk) {
webview.loadUrl("javascript:geoCallBackSuccess({coords:{'longitude':9.598470,'latitude':50.903697}})");
} else { webview.loadUrl("javascript:geoCallBackError({code:})");
}
}
Thanks
I believe you are missing just one semi colon in the string, semicolon after function name -
NSString * jsCallBack = [NSString stringWithFormat:#"geoCallBackSuccess(%#);",param];

Capture (and prevent) alert() modal in UIWebView [duplicate]

<script language="javascript">
alert("Hell! UIWebView!");
</script>
I can see the alert message inside my UIWebView but can I handle this situation?
Update:
I'm loading a web-page into my UIWebView:
- (void)login {
NSString *requestText = [[NSString alloc] initWithFormat: #"%#?user=%#&password=%#", DEFAULT_URL, user.name, user.password]; // YES, I'm using GET request to send password :)
NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:requestText]];
[webView loadRequest:request];
}
The target page contain a JS. If user name or password is incorrect this JS show alert.
I have not any access to its sources.
I want to handle it inside my UIWebViewDelegate.
A better solution to this problem is to create a Category for UIWebView for the method
webView:runJavaScriptAlertPanelWithMessage:initiatedByFrame:
So that you can handle the alert event in any way that you'd like. I did this because I don't like the default behavior of UIWebView when it puts the filename of the source in the UIAlertView title. The Category looks something like this,
#interface UIWebView (JavaScriptAlert)
- (void)webView:(UIWebView *)sender runJavaScriptAlertPanelWithMessage:(NSString *)message initiatedByFrame:(WebFrame *)frame;
#end
#implementation UIWebView (JavaScriptAlert)
- (void)webView:(UIWebView *)sender runJavaScriptAlertPanelWithMessage:(NSString *)message initiatedByFrame:(WebFrame *)frame {
UIAlertView* dialogue = [[UIAlertView alloc] initWithTitle:nil message:message delegate:nil cancelButtonTitle:#"Okay" otherButtonTitles:nil];
[dialogue show];
[dialogue autorelease];
}
#end
This seems to do it:
- (void)webViewDidFinishLoad:(UIWebView *)webView
{
JSContext *ctx = [webView valueForKeyPath:#"documentView.webView.mainFrame.javaScriptContext"];
ctx[#"window"][#"alert"] = ^(JSValue *message) {
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:#"JavaScript Alert" message:[message toString] delegate:nil cancelButtonTitle:#"OK" otherButtonTitles:nil];
[alert show];
};
}
Note: only tested on iOS 8.
If by "contain a flash" you mean the page you're loading into your web view has an Adobe Flash movie in it, you're out of luck, I'm afraid. Mobile Safari doesn't support Flash, and most likely never will.
In the general case, if you want JavaScript running in a web view to communicate with the native app hosting it, you can load fake URLs (for example: "myapp://alert?The+text+of+the+alert+goes+here."). That will trigger the webView:shouldStartLoadWithRequest:navigationType: delegate method. In that method, inspect the request, and if the URL being loaded is one of these internal communications, trigger the appropriate action in your app, and return NO.

Call javascript - click() in a WebView from Cocoa Objective-C code

In a MacOS X application, I create a Window containing a WebView.
The WebView is initialized on a html page that contains an anchor:
Go To Google.
I would like to click on that link from another class.
It seems clear that a simple javascript code would do the trick: document.getElementById("myLink").click();
So, I wrote that small objective-c code that should do it:
NSString *cmd = #"document.getElementById(\"myLink\").click();";
id result = [[attachedWebView windowScriptObject] evaluateWebScript:cmd];
if ([result isMemberOfClass:[WebUndefined class]]) {
NSLog(#"evaluation of <%#> returned WebUndefined", cmd)
But I can't make it work.
If anybody has an idea, that would really help.
I think it is nothing todo with webview, but just your javascript.
Does it work if you try it in Safari's console? I wouldn't expect it to as you can only click() input elements (buttons) reliably cross-browser. A JQuery click() should work tho.
see How do I programmatically click a link with javascript?
So here is the solution I used.
Created a file: WebAgent.js containing the following code:
function myClick(id) {
var fireOnThis = document.getElementById(id);
var evObj = document.createEvent('MouseEvents');
evObj.initEvent( 'click', true, true );
fireOnThis.dispatchEvent(evObj);
}
And the following code in my objective-c class
// load cmd.js
NSString *path = #"/code/testagent/WebAgent/WebAgent/WebAgent.js";
NSString *jsCode = [NSString stringWithContentsOfFile:path encoding:NSUTF8StringEncoding error:nil];
[[self attachedWebView ]stringByEvaluatingJavaScriptFromString:jsCode];
//do the click
NSString * anchorId = #"myId";
NSString *call = [NSString stringWithFormat:#"WebAgent_click('%#')",anchorId];
[[self attachedWebView] stringByEvaluatingJavaScriptFromString:call];
NB: I used this solution to have the JS code in a specific file, as I expect to have more JS code in the future.
Thanks for your help.

Categories

Resources