I want to use NativeScript's WebView to preprocess some of my data (it uses XHTML so I must use WebView and it's evaluateJavascript method).
But for the preprocess I don't want to render the whole WebView just for a blink of an eye time, so I created a service which creates a new WebView() instance. Now I can set the src attribute this way and all others, like height and with, but if I want to call the evaluateJavascript function I can't. Code below.
test() {
//create new webview instance
this.wv = new WebView();
//all of this works
this.wv.set('height', 100);
this.wv.setProperty('width', 200);
this.wv.setProperty('src', 'some url here');
//this does not work
this.wv.android.evaluateJavascript(
'function test(){ return 1;} test();',
new android.webkit.ValueCallback({
onReceiveValue: function (res: any) {
console.log(res);
}
})
);
}
The set and setProperty methods are fine, but the evaluateJavascript says it can't be called on undefined.
console.log(this.wv.android); //undefined
Also the WebView loaded event does not run, if have something like this in the test function above.
this.wv.on('loadFinished', (data) => {
console.log('loadFinished');
console.dir(data);
});
On the other hand, if I create a component, put a WebView element in it, than listen for the loadFinished event, than i can run the JS seen above, like this:
webViewLoaded(webargs) {
// console.log('WEBVIEW LOADED');
this.webView = webargs.object;
this.webVie.android.evaluateJavascript(
'function test(){ return 1;} test();',
new android.webkit.ValueCallback({
onReceiveValue: function (res: any) {
console.log(res);
}
})
);
}
//It run properly.
}
So my questions are:
Am I right and the loadFinished event does not run on the creation of new WebView() instance? If it does run, how can I add a listener to it?
What's the difference between the instance of new WebView() and the object returned in the args param above? And why this.wv.android is undefined in the test function above?
Can I run evaluateJavascript on a WebView instance that does not have any HTML counterpart and is not even rendered out? If yes, how?
Am I obligated to render the WebView to use it properly?
Related
UPDATE: it's a confirmed bug. Please upvote it here because it doesn't really receive a lot of attention from MS.
I need to override the shouldInterceptRequest method of WebViewClient to load in-app HTML content following that guide.
Here's the repo with the reproducible code: GitHub. I took a sample code from MS Q&A as well:
// ...
.ConfigureMauiHandlers(handlers =>
{
handlers.AddHandler<Microsoft.Maui.Controls.WebView, ProblemHandler2>();
});
// ...
internal class ProblemHandler2 : WebViewHandler
{
protected override Android.Webkit.WebView CreatePlatformView()
{
var wv = new Android.Webkit.WebView(Android.App.Application.Context);
wv.SetWebViewClient(new CustomWebClient());
return wv;
}
}
In the repo, I included 2 custom handlers:
ProblemHandler2 is the exact snippet by the MSFT. I realized a problem: Setting MAUI WebView's Source property no longer navigates the real Android WebView:
WebViewHandler.Mapper.AppendToMapping("MyHandler", (handler, view) =>
{
#if ANDROID
var xWv = handler.PlatformView;
// For ProblemHandler2, this is needed to actually navigate:
xWv.LoadUrl("https://www.google.com/");
#endif
});
this.wv.Source = "https://www.google.com/";
ProblemHandler1 uses the default result and adds a custom handler. This fixes the navigation problem, but, both problem have the same issue:
ShouldInterceptRequest is never called. It is never called on anything even when I manually click a link to navigate. What am I missing? I am sure the CustomWebClient is correctly created and set.
I noticed none of the other callbacks works as well, for example:
public override void OnPageStarted(Android.Webkit.WebView view, string url, Bitmap favicon)
{
Debugger.Break();
Debug.WriteLine(url);
base.OnPageStarted(view, url, favicon);
}
public override void OnPageFinished(Android.Webkit.WebView view, string url)
{
Debugger.Break();
Debug.WriteLine(url);
base.OnPageFinished(view, url);
}
I also tried using WebViewHandler.Mapping but it also does not work:
WebViewHandler.Mapper.AppendToMapping("MyHandler", (handler, _) =>
{
#if ANDROID
handler.PlatformView.SetWebViewClient(new CustomWebClient());
#endif
});
I could be wrong but, I think this might have to do with your overridden version of the CreatePlatform method,
Can you try what the default WebViewHandler is doing:
protected override WebView CreatePlatformView()
{
var platformView = new MauiWebView(this, Context!)
{
LayoutParameters = new LayoutParams(LayoutParams.MatchParent, LayoutParams.MatchParent)
};
platformView.Settings.JavaScriptEnabled = true;
platformView.Settings.DomStorageEnabled = true;
platformView.Settings.SetSupportMultipleWindows(true);
return platformView;
}
Check this URL for the default handlers CreatePlatform setup :
https://github.com/dotnet/maui/blob/c6250a20d73e1992b4a02e6f3c26a1e6cbcbe988/src/Core/src/Handlers/WebView/WebViewHandler.Android.cs
Also don't use Application Context in Handlers, Handlers have their own Context property you can use.
Yes, it is the case as you said.
And I have created a new issue for this problem, you can follow it up here: https://github.com/dotnet/maui/issues/11004.
Thanks for your support and feedback for maui.
Best Regards.
i want to delete an element in Xamarin.forms WebView when it's loading but it return null and app crash . how can i do this?
i tried to check if an element exist but dont have any success.
this is my code
protected void OnNavigating(object sender, WebNavigatingEventArgs args)
{
Webview.Eval("const elements = document.getElementsByClassName(\"footer-section\"); while (elements.length > 0) elements[0].remove();");
}
please help me. thanks
You are getting a null exception because the WebView is not loaded when you try to execute Javascript.
In order to prevent this, you can subscribe to OnNavigated Event:
WebView.Navigated Event
Event that is raised after navigation completes.
So, here is a sample:
public YourWebViewPage()
{
InitializeComponent ();
Webview.Navigated += WebViewNavigated;
}
private void WebViewNavigated(object sender, WebNavigatedEventArgs e)
{
Webview.Eval("const elements = document.getElementsByClassName(\"footer-section\"); while (elements.length > 0) elements[0].remove();");
}
You can try to override function onPageCommitVisible
The Android documentation says:
This callback can be used to determine the point at which it is safe
to make a recycled WebView visible, ensuring that no stale content is
shown. It is called at the earliest point at which it can be
guaranteed that WebView#onDraw will no longer draw any content from
previous navigations. The next draw will display either the
WebView#setBackgroundColor of the WebView, or some of the contents of
the newly loaded page.
This method is called when the body of the HTTP response has started
loading, is reflected in the DOM, and will be visible in subsequent
draws. This callback occurs early in the document loading process, and
as such you should expect that linked resources (for example, CSS and
images) may not be available.
You can try the following code:
public override void OnPageCommitVisible(WebView view, string url)
{
string _javascript = "const elements =
document.getElementsByClassName('footer-section'); for(i=0;i<elements.length;i++) {
if(elements[i] != null){ elements[i].parentNode.removeChild(elements[i]); }}";
view.EvaluateJavascript(_javascript, null);
base.OnPageCommitVisible(view, url);
}
I am working on a hybrid app and trying to return true or false in onOverrideUrlLoading of webview based on returned value from javascript function executed in webview
Example code I have so far.
//Have a boolean variable isExternalDomain;
//Added JavascriptInterface webView.addJavascriptInterface(this, "android");
public boolean onOverrideUrlLoading(final String url) {
WebView.loadUrl("javascript:android.onData('true')");
//I Tried inserting sleep, delay EG: Thread.sleep(200);
//I see the delay but still javascript executes last.
if(isExternalDomain) {
return true;
} else {
return false;
}
}
#JavascriptInterface public void onData(String value)
{
isExternalDomain = true;
}
So the Issue I am having is javascript execution happens after onOverrideUrlLoading completed executing all lines with isExternalDomain as false. I would like to have onOverrideUrlLoading returning true or false based on javascript returned value.
Unfortunately, running JavaScript code from inside onOverrideUrlLoading() isn't possible. You must return from onOverrideUrlLoading() before WebView can do anything else. When you call WebView.loadUrl() from inside onOverrideUrlLoading(), what really happens is an asynchronous task gets posted onto the WebView's message loop. It only gets processed after you leave onOverrideUrlLoading(). Thus, no amount of delay will make WebView to process your request while your code is inside onOverrideUrlLoading().
If you want to prevent navigation from happening based on the decision made by JavaScript code, it's more natural to do that on the JavaScript side by using window.onbeforeunload event handler. If you return non-null value from it, an attempt to navigate away by clicking a link will be cancelled.
Below is a sample of JavaScript code:
window.onbeforeunload = function() {
if (navigationDisallowed()) {
return true; // Prevent navigating away from the page.
} else {
return null; // Allow navigating away.
}
}
I'm working on making a browser as a hybrid app using worklight framework for Android. I implemented my address bar as an input element which received the user input and pass the arguments to the webview to load the page.
However, I cannot figure out how to do the reverse: whenever the user click on a link in webview, I want the address bar to change to the new location.
Are you implementing a native page that is opened? If so, take a look at ChildBrowser, that basically does the same thing. It has a TextView being used as an address bar. You may decide to use it, or get the bits and pieces you want out of it. Regardless, I would image what you want to do something like this. By overriding the onLoadResource in the WebViewClient, you should be able to grab the url and change your TextBox.
In response to the comment below: inside your environment's main js file in the wlEnvInit() function:
function wlEnvInit(){
wlCommonInit();
// Environment initialization code goes here
document.onclick=manageLinks;
}
Then in this function get the url and set the text of your input element:
function manageLinks(event) {
var link = event.target;
//go up the family tree until we find the A tag
while (link && link.tagName != 'A') {
link = link.parentNode;
}
if (link) {
var url = link.href;
console.log("url = " + url);
//You can decide if you want to separate external or
//internal links, depending on your application
var linkIsExternal = ((url.indexOf('http://') == 0) || (url.indexOf('https://') == 0));
if (linkIsExternal) {
myInput.setText(url);
return false;
}
}
return true;
}
Inside of your WebView, inside the plugin, intercept the URL like this:
webview.setWebViewClient(new WebViewClient() {
#Override
public void onPageFinished(WebView view, String url) {
//use this area to set your input. Depending on how you
//implemented your plugin, you may need to return this value
//back to your main activity
Toast.makeText(cordova.getActivity(), "Loading: " + url, Toast.LENGTH_LONG).show();
}
});
Have you try to get the url from the href of and assign to the input variable and do the get/post? I know that it is possible in SDK i figure it dont will be harder in a framework. You can store the hiperlinks in a array with a parser or something similar.
example pseudocode:
When_hiperlink_clicked: //could be like a listener (search about it)
url = hiperlink.getURL("myHiperlink");
myinput.setText(url);
execute_input_bar_action();
Is difficult to figure out without code or something more, sorry.
What I'm trying to do:
user fills out a form data, clicks submit.
user presented with a loading screen. (HTML element).
Application makes asynchronous call, PhoneGap plugin, which saves this data in db. That's where the problem is, because the call is synchronous instead.
When html app receives a callback, I hide loading screen.
Because of synchronous call that's what I got:
user fills out a form, submits
HTML app freezes, data is being saved to a database.
loading screen appears
callback is called, a few milliseconds after.
Here's some demo (trimmed) code:
Java:
public class SomePlugin extends Plugin
...
public PluginResult execute(String action, JSONArray data, String callbackId)
{
PluginResult result = null;
//
// save data in the background...
//
Log.d("TAG", "Some Message...");
result = new PluginResult(Status.OK, "");
// or
// result = new PluginResult(Status.ERROR);
return result;
}
...
public boolean isSynch(String action) {
return false; // always do async...
}
JavaScript:
$('#loading-screen').show();
var successCallback = function() {
console.log('Success Callback');
$('#loading-screen').hide();
};
var failureCallback = function() {
console.log('Failed Callback');
$('#loading-screen').hide();
};
PhoneGap.exec(successCallback, failureCallback, 'PluginName', 'actionName', data);
From PhoneGap source:
* Execute a PhoneGap command. It is up to the native side whether this action is synch or async.
* The native side can return:
* Synchronous: PluginResult object as a JSON string
* Asynchrounous: Empty string ""
* If async, the native side will PhoneGap.callbackSuccess or PhoneGap.callbackError,
* depending upon the result of the action.
So I thought maybe this line is incorrect in that case:
new PluginResult(Status.OK, "");
Note: If wrap (JavaScript) PhoneGap.exec call in setTimeout (with a delay of 1 sec for example), loading screen will work "properly" (it's still frozen but user have an instant feedback), but that's obviously not a solution.
I think I just not seeing something obvious here, just one parameter or something somewhere.
Thanks.
I think its perfect for an AsyncTask
just process you dbStorage in doInBackground and handle finishing in onPostExcecute().
You are free to update status in onProgressUpdate