NullPointerException in JxBrowser when passing null from JavaScript to Java - javascript

The following code causes a NullPointerException within the JxBrowser (Version 6.14) java code which i can not debug any further, when passing null as argument to a java-method.
In the documentation it says null converts to null, so i assume it should work and it's a bug: https://jxbrowser.support.teamdev.com/support/solutions/articles/9000013062-calling-java-from-javascript (table at the bottom)
Any ideas how to "fix" this?
import com.teamdev.jxbrowser.chromium.Browser;
import com.teamdev.jxbrowser.chromium.BrowserPreferences;
import com.teamdev.jxbrowser.chromium.JSObject;
import com.teamdev.jxbrowser.chromium.LoggerProvider;
import com.teamdev.jxbrowser.chromium.events.ConsoleEvent;
import com.teamdev.jxbrowser.chromium.events.ConsoleListener;
import com.teamdev.jxbrowser.chromium.events.ScriptContextEvent;
import com.teamdev.jxbrowser.chromium.events.ScriptContextListener;
import com.teamdev.jxbrowser.chromium.swing.BrowserView;
import java.awt.BorderLayout;
import java.util.logging.Level;
import javax.swing.JFrame;
import javax.swing.WindowConstants;
public class SSCCE_JxBrowser {
public static void main(String[] args) {
LoggerProvider.setLevel(Level.INFO); // ALL
BrowserPreferences.setChromiumSwitches("--remote-debugging-port=9222");
Browser browser = new Browser();
BrowserView browserView = new BrowserView(browser);
JFrame frame = new JFrame();
frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
frame.add(browserView, BorderLayout.CENTER);
frame.setSize(700, 500);
frame.setLocationRelativeTo(null);
frame.setTitle("JxBrowser Test");
frame.setVisible(true);
browser.addScriptContextListener(new ScriptContextListener() {
#Override
public void onScriptContextDestroyed(ScriptContextEvent event) {}
#Override
public void onScriptContextCreated(ScriptContextEvent event) {
JSObject window = event.getBrowser().executeJavaScriptAndReturnValue("window").asObject();
window.setProperty("java", new JavaObject());
}
});
browser.addConsoleListener(new ConsoleListener() {
#Override
public void onMessage(ConsoleEvent event) {
System.out.println("log: " + event);
}
});
browser.loadHTML("<html><head>" //
+ "<script>" //
+ "java.log('Ok!');" // this is OK
+ "java.log(null);" // this fails with NullPointerException
+ "</script>" //
+ "<body>page loaded - <a href=\"" + browser.getRemoteDebuggingURL() + "\" target=_blank>debug</a>"
+ "</body></html>");
}
public static class JavaObject {
public void log(String msg) {
System.out.println("msg: " + msg);
}
}
}
Output (html):
Uncaught java.lang.NullPointerException: null
Output (java):
msg: Ok!
log: ConsoleEvent{lineNumber=1, message='Uncaught java.lang.NullPointerException: null', source='about:blank'}
Using an exception breakpoint i found this stacktrace:
Thread [IPC Sync Events Thread] (Suspended (exception java.lang.NullPointerException))
com.teamdev.jxbrowser.chromium.JSContext.a(com.teamdev.jxbrowser.chromium.JSContext, com.teamdev.jxbrowser.chromium.internal.ipc.message.OnInvokeJSJavaMessage) line: 2608
com.teamdev.jxbrowser.chromium.JSContext$a.onMessageReceived(com.teamdev.jxbrowser.chromium.internal.ipc.message.Message) line: 280
com.teamdev.jxbrowser.chromium.internal.ipc.p.a(com.teamdev.jxbrowser.chromium.internal.ipc.p, com.teamdev.jxbrowser.chromium.internal.ipc.message.Message) line: 1082
com.teamdev.jxbrowser.chromium.internal.ipc.q.run() line: 66
com.teamdev.jxbrowser.chromium.internal.q.run() line: 63
java.util.concurrent.Executors$RunnableAdapter<T>.call() line: 511
java.util.concurrent.FutureTask<V>.run() line: 266
java.util.concurrent.ThreadPoolExecutor.runWorker(java.util.concurrent.ThreadPoolExecutor$Worker) line: 1142
java.util.concurrent.ThreadPoolExecutor$Worker.run() line: 617
java.lang.Thread.run() line: 748

This issue has been fixed in JxBrowser 6.19.2-b1-eap build available for download. The fix will be included into the next official release.

You are welcome to download JxBrowser 6.20 with the fix included.

Related

Selenium test not failing correctly - Pass with configuration fail rather than "proper" fail

Hi I'm still newbie to Selenium/Scripting/Java and I'me still at the stage of hacking together code from elsewhere to get stuff work( tutorials and recorders mainly)
Anyway Im trying to write a script to check a particular 'element' is present (I will want to the the reverse as well) I can get the script to pass correctly when it finds the 'element' but if change the element details so I know it should fail (as it don't exist) TestNG still pass's the test but gives a configuration fail ?
I presume I'm missing something to cover the fail aspect of the test but no sure how to go about it, every time I try I and get it to run into this.
package Links;
import org.testng.annotations.*;
import static org.testng.Assert.*;
import org.openqa.selenium.*;
import org.openqa.selenium.firefox.FirefoxDriver;
public class TestArea {
private WebDriver driver;
private StringBuffer verificationErrors = new StringBuffer();
#BeforeClass(alwaysRun = true)
public void setUp() throws Exception {
System.setProperty("webdriver.gecko.driver", "C:\\Automation\\SeleniumFiles\\Browser Drivers\\geckodriver.exe");
driver = new FirefoxDriver();
}
#Test
public void Example() throws Exception {
driver.get(
"http://MyWebsite");
try {
assertTrue(isElementPresent(
By.xpath("The Element I want look for ")));
} catch (Error e) {
verificationErrors.append(e.toString());
}
}
// -------------------------------------------------------------------------------------
#AfterClass(alwaysRun = true)
public void tearDown() throws Exception {
driver.quit();
String verificationErrorString = verificationErrors.toString();
if (!"".equals(verificationErrorString)) {
fail(verificationErrorString);
}
}
private boolean isElementPresent(By by) {
try {
driver.findElement(by);
return true;
} catch (NoSuchElementException e) {
return false;
}
}
}
An example of a "passed" test but with a failed configuration.
FAILED CONFIGURATION: #AfterClass tearDown java.lang.AssertionError:
java.lang.AssertionError: expected [true] but found [false] at
org.testng.Assert.fail(Assert.java:96) at
Links.TestArea.tearDown(TestArea.java:39) at
sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at
sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source) at
sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source) at
java.lang.reflect.Method.invoke(Unknown Source) at
org.testng.internal.MethodInvocationHelper.invokeMethod(MethodInvocationHelper.java:124)
at
org.testng.internal.MethodInvocationHelper.invokeMethodConsideringTimeout(MethodInvocationHelper.java:59)
at
org.testng.internal.Invoker.invokeConfigurationMethod(Invoker.java:455)
at org.testng.internal.Invoker.invokeConfigurations(Invoker.java:222)
at org.testng.internal.Invoker.invokeConfigurations(Invoker.java:142)
at
org.testng.internal.TestMethodWorker.invokeAfterClassMethods(TestMethodWorker.java:214)
at
org.testng.internal.TestMethodWorker.run(TestMethodWorker.java:111)
at org.testng.TestRunner.privateRun(TestRunner.java:648) at
org.testng.TestRunner.run(TestRunner.java:505) at
org.testng.SuiteRunner.runTest(SuiteRunner.java:455) at
org.testng.SuiteRunner.runSequentially(SuiteRunner.java:450) at
org.testng.SuiteRunner.privateRun(SuiteRunner.java:415) at
org.testng.SuiteRunner.run(SuiteRunner.java:364) at
org.testng.SuiteRunnerWorker.runSuite(SuiteRunnerWorker.java:52) at
org.testng.SuiteRunnerWorker.run(SuiteRunnerWorker.java:84) at
org.testng.TestNG.runSuitesSequentially(TestNG.java:1208) at
org.testng.TestNG.runSuitesLocally(TestNG.java:1137) at
org.testng.TestNG.runSuites(TestNG.java:1049) at
org.testng.TestNG.run(TestNG.java:1017) at
org.testng.remote.AbstractRemoteTestNG.run(AbstractRemoteTestNG.java:114)
at org.testng.remote.RemoteTestNG.initAndRun(RemoteTestNG.java:251)
at org.testng.remote.RemoteTestNG.main(RemoteTestNG.java:77)
PASSED: Example
===============================================
Default test
Tests run: 1, Failures: 0, Skips: 0
Configuration Failures: 1, Skips: 0
=============================================== Default suite Total tests run: 1, Failures: 0, Skips: 0 Configuration Failures: 1, Skips:
0
I don't get the configuration issue when the test can find the element.
Thanks very much in advance
There are a few issues in your test code.
TestNG by default fails a #Test method when :
An assertion fails
test method raises an Exception.
So you don't need to wrap assertTrue() call within a try..catch block. If you would like to run through all assertions and have the test method fail at the end, you should be using something called as Soft Assertion in TestNG.
Below is a cleaned-up version of your test code.
import org.testng.annotations.*;
import static org.testng.Assert.*;
import org.openqa.selenium.*;
import org.openqa.selenium.firefox.FirefoxDriver;
public class TestArea {
private WebDriver driver;
#BeforeClass(alwaysRun = true)
public void setUp() throws Exception {
System.setProperty("webdriver.gecko.driver", "C:\\Automation\\SeleniumFiles\\Browser Drivers\\geckodriver.exe");
driver = new FirefoxDriver();
}
#Test
public void Example() throws Exception {
driver.get( "http://MyWebsite");
assertTrue(isElementPresent( By.xpath("The Element I want look for ")));
}
#AfterClass(alwaysRun = true)
public void tearDown() throws Exception {
driver.quit();
}
private boolean isElementPresent(By by) {
try {
driver.findElement(by);
return true;
} catch (NoSuchElementException e) {
return false;
}
}
}

JavaFx WebView wait for Java method to finish

I am calling a Java method from JavaScript in a JavaFx WebView. The Java method does some work, i.e. does not return immediately. What is a natural way to wait for the method to finish from JavaScript?
Since everything is executed on the FX Application Thread, the Java method you call needs to run the long-running process on a background thread (otherwise you will make the UI unresponsive). You can pass the method a Javascript function to call when it is complete: note that this javascript function needs to be called on the FX Application Thread. One way is to wrap the call in Platform.runLater(), but using a Task makes things a bit cleaner.
Here is a SSCCE:
import javafx.application.Application;
import javafx.concurrent.Task;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox;
import javafx.scene.web.WebView;
import javafx.stage.Stage;
import netscape.javascript.JSObject;
public class WebViewCallbackTest extends Application {
private static final String HTML =
"<html>"
+ " <head>"
+ " <script>"
+ ""
+ " function doCall() {"
+ " javaApp.doLongRunningCall('updateResults');"
+ " }"
+ ""
+ " function updateResults(results) {"
+ " document.getElementById('results').innerHTML = results ;"
+ " }"
+ ""
+ " </script>"
+ " </head>"
+ " <body>"
+ " <div>"
+ " Result of call:"
+ " </div>"
+ " <div id='results'></div>"
+ " </body>"
+ "</html>";
private Button button;
private JSObject window;
#Override
public void start(Stage primaryStage) {
WebView webView = new WebView();
webView.getEngine().loadContent(HTML);
BorderPane root = new BorderPane(webView);
window = (JSObject) webView.getEngine().executeScript("window");
window.setMember("javaApp", this);
button = new Button("Run process");
button.setOnAction(e -> webView.getEngine().executeScript("doCall()"));
HBox controls = new HBox(button);
controls.setAlignment(Pos.CENTER);
controls.setPadding(new Insets(5));
root.setBottom(controls);
Scene scene = new Scene(root, 600, 600);
primaryStage.setScene(scene);
primaryStage.show();
}
public void doLongRunningCall(String callback) {
Task<String> task = new Task<String>() {
#Override
public String call() throws InterruptedException {
Thread.sleep(2000);
return "The answer is 42";
}
};
task.setOnSucceeded(e ->
window.call(callback, task.getValue()));
task.setOnFailed(e ->
window.call(callback, "An error occurred"));
button.disableProperty().bind(task.runningProperty());
new Thread(task).start();
}
public static void main(String[] args) {
launch(args);
}
}
(There may be an easier way than this: I am not an expert on Webview Javascript <-> Java communication, but this seems OK to me.)

Android main thread blocking WebView thread

I've been working on a problem with doing a synchronous call to JavaScript in a WebView (with a return value) and trying to narrow down the where and why of why it's not working. It seems to be that the WebView thread is blocking while the main thread is waiting for a response from it -- which shouldn't be the case since theWebView runs on a separate thread.
I've put together this small sample that demonstrates it (I hope) fairly clearly:
main.xml:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:weightSum="1">
<WebView
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:id="#+id/webView"/>
</LinearLayout>
MyActivity.java:
package com.example.myapp;
import android.app.Activity;
import android.os.Build;
import android.os.Bundle;
import android.util.Log;
import android.webkit.WebSettings;
import android.webkit.WebView;
import android.webkit.JavascriptInterface;
import android.webkit.WebViewClient;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
public class MyActivity extends Activity {
public final static String TAG = "MyActivity";
private WebView webView;
private JSInterface JS;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
webView = (WebView)findViewById(R.id.webView);
JS = new JSInterface();
webView.addJavascriptInterface(JS, JS.getInterfaceName());
WebSettings settings = webView.getSettings();
settings.setJavaScriptEnabled(true);
webView.setWebViewClient(new WebViewClient() {
public void onPageFinished(WebView view, String url) {
Log.d(TAG, JS.getEval("test()"));
}
});
webView.loadData("<script>function test() {JSInterface.log(\"returning Success\"); return 'Success';}</script>Test", "text/html", "UTF-8");
}
private class JSInterface {
private static final String TAG = "JSInterface";
private final String interfaceName = "JSInterface";
private CountDownLatch latch;
private String returnValue;
public JSInterface() {
}
public String getInterfaceName() {
return interfaceName;
}
// JS-side functions can call JSInterface.log() to log to logcat
#JavascriptInterface
public void log(String str) {
// log() gets called from Javascript
Log.i(TAG, str);
}
// JS-side functions will indirectly call setValue() via getEval()'s try block, below
#JavascriptInterface
public void setValue(String value) {
// setValue() receives the value from Javascript
Log.d(TAG, "setValue(): " + value);
returnValue = value;
latch.countDown();
}
// getEval() is for when you need to evaluate JS code and get the return value back
public String getEval(String js) {
Log.d(TAG, "getEval(): " + js);
returnValue = null;
latch = new CountDownLatch(1);
final String code = interfaceName
+ ".setValue(function(){try{return " + js
+ "+\"\";}catch(js_eval_err){return '';}}());";
Log.d(TAG, "getEval(): " + code);
// It doesn't actually matter which one we use; neither works:
if (Build.VERSION.SDK_INT >= 19)
webView.evaluateJavascript(code, null);
else
webView.loadUrl("javascript:" + code);
// The problem is that latch.await() appears to block, not allowing the JavaBridge
// thread to run -- i.e., to call setValue() and therefore latch.countDown() --
// so latch.await() always runs until it times out and getEval() returns ""
try {
// Set a 4 second timeout for the worst/longest possible case
latch.await(4, TimeUnit.SECONDS);
} catch (InterruptedException e) {
Log.e(TAG, "InterruptedException");
}
if (returnValue == null) {
Log.i(TAG, "getEval(): Timed out waiting for response");
returnValue = "";
}
Log.d(TAG, "getEval() = " + returnValue);
return returnValue;
}
// eval() is for when you need to run some JS code and don't care about any return value
public void eval(String js) {
// No return value
Log.d(TAG, "eval(): " + js);
if (Build.VERSION.SDK_INT >= 19)
webView.evaluateJavascript(js, null);
else
webView.loadUrl("javascript:" + js);
}
}
}
When running, the following results:
Emulator Nexus 5 API 23:
05-25 13:34:46.222 16073-16073/com.example.myapp D/JSInterface: getEval(): test()
05-25 13:34:50.224 16073-16073/com.example.myapp I/JSInterface: getEval(): Timed out waiting for response
05-25 13:34:50.224 16073-16073/com.example.myapp D/JSInterface: getEval() =
05-25 13:34:50.225 16073-16073/com.example.myapp I/Choreographer: Skipped 239 frames! The application may be doing too much work on its main thread.
05-25 13:34:50.235 16073-16150/com.example.myapp I/JSInterface: returning Success
05-25 13:34:50.237 16073-16150/com.example.myapp D/JSInterface: setValue(): Success
(16073 is 'main'; 16150 is 'JavaBridge')
As you can see, the main thread times out waiting for theWebView to call setValue(), which it doesn't until latch.await() has timed out and main thread execution has continued.
Interestingly, trying with an earlier API level:
Emulator Nexus S API 14:
05-25 13:37:15.225 19458-19458/com.example.myapp D/JSInterface: getEval(): test()
05-25 13:37:15.235 19458-19543/com.example.myapp I/JSInterface: returning Success
05-25 13:37:15.235 19458-19543/com.example.myapp D/JSInterface: setValue(): Success
05-25 13:37:15.235 19458-19458/com.example.myapp D/JSInterface: getEval() = Success
05-25 13:37:15.235 19458-19458/com.example.myapp D/MyActivity: Success
(19458 is 'main'; 19543 is 'JavaBridge')
Things work correctly in sequence, with getEval() causing the WebView to call setValue(), which then exits latch.await() before it times out (as you'd expect/hope).
(I've also tried with an even earlier API level, but things crash out due to what may be, as I understand it, an emulator-only bug in 2.3.3 that never got fixed.)
So I'm at a bit of a loss. In digging around, this seems like the correct approach to doing things. It certainly seems like the correct approach because it works properly on API level 14. But then it's failing on later versions — and I've tested on 5.1 and 6.0 without success.
Look more about migration WebView with Android 4.4.
See description on Android Docs I think you need to use another method for funning your JS action.
For example, base on that doc - Running JS Async Asynchronously evaluates JavaScript in the context of the currently displayed page. If non-null, |resultCallback| will be invoked with any result returned from that execution. This method must be called on the UI thread and the callback will be made on the UI thread.

How to display javascript console.log in eclipse console?

Title says it all. I am wondering if i can display javascript console.log in eclipse console rather than web browser's dev console?
Just found an article regarding this.
This is How it works(For Window 7).
Install Node.js javascript engine at Node.js
Open your Eclipse, in the menu
Run->External Tools->External Tools Configuration
Create new launch configuration under program category.
Set
Location : C:\WINDOWS\system32\cmd.exe
Working Directory : C:\WINDOWS\system32
Argument : /c "node ${resource_loc}"
Now create new environment variable 'node' refers to node.exe file(wherever you installed)
All done.
Redirect javascript console.logs, in Java console
Here is my solution to get javascript console messages in Java (with SWT browser)
create shell SWT and SWT browser see: Shell + Browser
create custom function SWT see: call Java from JavaScript
Add listener on error events in javascript see: mdn event error
Override console object in javascript and call custom java function (2.)
Here is my example snippet:
import org.eclipse.swt.SWT;
import org.eclipse.swt.SWTError;
import org.eclipse.swt.browser.Browser;
import org.eclipse.swt.browser.BrowserFunction;
import org.eclipse.swt.browser.LocationAdapter;
import org.eclipse.swt.browser.LocationEvent;
import org.eclipse.swt.browser.ProgressListener;
import org.eclipse.swt.layout.FillLayout;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Shell;
public class Snippet307d3 {
public static final Shell createShell() {
final var display = new Display();
final var shell = new Shell(display);
shell.setText("Snippet DEBUG");
shell.setLayout(new FillLayout());
shell.setBounds(10, 10, 300, 200);
return shell;
}
public static final Browser createBrowser(Shell shell) {
try {
return new Browser(shell, SWT.NONE);
} catch (final SWTError e) {
System.out.println("Could not instantiate Browser: " + e.getMessage());
shell.getDisplay().dispose();
System.exit(-1);
return null;
}
}
public static final void runShell(Shell shell) {
shell.open();
final var display = shell.getDisplay();
while (!shell.isDisposed())
if (!display.readAndDispatch())
display.sleep();
display.dispose();
}
public static void main(String[] args) {
// -> Create shell
final var shell = createShell();
// -> Create browser
final var browser = createBrowser(shell);
browser.setJavascriptEnabled(true);
// -> set HTML or use setUrl
browser.setText(createHTML());
// browser.setUrl(URL_DOCUMENT_HTML_TEST);
// -> Create custom function
final BrowserFunction function = new CustomFunction(browser, "theJavaFunctionDebugInEclipse");
// -> Register function for cleanup
browser.addProgressListener(ProgressListener.completedAdapter(event -> {
browser.addLocationListener(new LocationAdapter() {
#Override
public void changed(LocationEvent event) {
browser.removeLocationListener(this);
System.out.println("left java function-aware page, so disposed CustomFunction");
function.dispose();
}
});
}));
// -> 6) Start shell
runShell(shell);
}
private static class CustomFunction extends BrowserFunction {
public CustomFunction(Browser browser, String name) {
super(browser, name);
}
#Override
public Object function(Object[] arguments) {
for (final Object v : arguments)
if (v != null)
System.out.println(v.toString());
return new Object();
}
}
private static String createHTML() {
return """
<!DOCTYPE>
<html lang='en'>
<head>
<title>DEBUG SWT</title>
<script>
const console = {
log : function(args) {
try {
theJavaFunctionDebugInEclipse('redirect > ' + args);
} catch (_e) {
return;
}
},
error : function(args) {
this.log(args);
},
exception : function(args) {
this.log(args);
},
debug : function(args) {
this.log(args);
},
trace : function(args) {
this.log(args);
},
info : function(args) {
this.log(args);
}
};
window.addEventListener('error', function(e) {
console.log(e.type + ' : ' + e.message);
console.log(e);
});
</script>
</head>
<body>
<input id=button type='button' value='Push to Invoke Java'
onclick='function1();'>
<p>
<a href='http://www.eclipse.org'>go to eclipse.org</a>
</p>
<script>
// bad char sequence .. send error
eeeee
function function1() {
let result;
try {
// Call bad function java .. send log
result = badFunctionJava(12, false, null, [ 3.6,
[ 'swt', true ] ], 'eclipse');
} catch (e) {
console.log('a error occurred: ' + e.message);
return;
}
}
</script>
</body>
</html>
""";
}
}
Further to #ringord's answer here, these would be the commands for your External Tools Configuration on Linux:
Location : /home/<user>/.nvm/versions/node/<version>/bin/node (or wherever you installed node)
Working Directory : /home/<user>
Arguments : ${container_loc}/${resource_name}

Android webview, loading javascript file in assets folder

I've seen this question has been asked a lot of times, but still can't manage to get my code working.
I want my webview to load some URL (say www.google.com) and then apply some javascript stored in assets/jstest.js, which contains the following:
function test(){
document.bgColor="#00FF00"; //turns to green the background color
}
And here's where I try to load the JS:
#Override
public void onPageFinished(WebView view, String url){
view.loadUrl("javascript:(function() { "
+ " document.bgColor='#FF0000';" //turns to red the background color
+ " var script=document.createElement('script'); "
+ " script.setAttribute('type','text/javascript'); "
+ " script.setAttribute('src', 'file:///android_asset/jstest.js'); "
+ " script.onload = function(){ "
+ " test(); "
+ " }; "
+ " document.getElementsByTagName('head')[0].appendChild(script); "
+ "})()");
}
I know the javascript here works because the background color actually turns to red, but for some reason it won't load jstest.js. I think the problem might be in file path (I'm certain every other line of the javascript code is correct), but it looks correct to me. And the file is in the right folder.
What am I missing?
EDIT:
Since WebResourceResponse class is available only with API Level 11, here's what I've figured out in the end.
public void onPageFinished(WebView view, String url){
String jscontent = "";
try{
InputStream is = am.open("jstest.js"); //am = Activity.getAssets()
InputStreamReader isr = new InputStreamReader(is);
BufferedReader br = new BufferedReader(isr);
String line;
while (( line = br.readLine()) != null) {
jscontent += line;
}
is.close();
}
catch(Exception e){}
view.loadUrl("javascript:(" + jscontent + ")()");
}
with the jstest.js simply containing:
function() {
document.bgColor="#00FF00";
}
I tried the same thing, loading a bookmarklet (the javascript code in your loadUrl() call) into a third-party page. My bookmarklet also depends on other assets (javascript and css files) which would not load with a file:///android_asset URL.
That's because the security context of the page is still that of, e.g., http://www.google.com, and that's not allowed access to file: URLs. You should be able to see the errors if you supply/override a WebChromeClient.onConsoleMessage().
I ended up with a kludge where I changed the bookmarklet's asset references to a bogus URL scheme, like:
asset:foo/bar/baz.js
and added a WebViewClient.shouldInterceptRequest() override which looks for those and loads them from assets using AssetManager.open().
One thing I don't like about this kludge is that the asset: scheme is open to any third-party HTML/Javascript on any page my view loads, giving them access to my app's assets.
One alternative, which I didn't try, would be to embed the sub-assets in the bookmarklet using data: URLs, but that can get unwieldy.
I'd much prefer it if there was a way to manipulate the security context of just the JS bookmarklet I'm loading in loadUrl(), but I can't find anything like that.
Here's a snippet:
import android.webkit.WebResourceResponse;
...
private final class FooViewClient extends WebViewClient
{
private final String bookmarklet;
private final String scheme;
private FooViewClient(String bookmarklet, String scheme)
{
this.bookmarklet = bookmarklet;
this.scheme = scheme;
}
#Override
public void onPageFinished(WebView view, String url)
{
view.loadUrl(bookmarklet);
}
#Override
public WebResourceResponse shouldInterceptRequest(WebView view, String url)
{
if (url.startsWith(scheme))
try
{
return new WebResourceResponse(url.endsWith("js") ? "text/javascript" : "text/css", "utf-8",
Foo.this.getAssets().open(url.substring(scheme.length())));
}
catch (IOException e)
{
Log.e(getClass().getSimpleName(), e.getMessage(), e);
}
return null;
}
}
I think the iceam cream webview client of cordova does the very thing you want to do.
It would be nice if this was documented somewhere but, as far as I can see, it is not.
Take a look at cordova's android github:
https://github.com/apache/incubator-cordova-android/blob/master/framework/src/org/apache/cordova/IceCreamCordovaWebViewClient.java
Here is how i ended up doing it. I used the Content:// protocol and set up a contentprovider to handle returning a file descriptor to the system
Here is my fileContentProvider:
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import org.apache.commons.io.IOUtils;
import android.content.ContentProvider;
import android.content.ContentValues;
import android.database.Cursor;
import android.net.Uri;
import android.os.ParcelFileDescriptor;
import android.util.Log;
public class FileContentProvider extends ContentProvider {
#Override
public ParcelFileDescriptor openFile(Uri uri, String mode) {
Log.d("FileContentProvider","fetching: " + uri);
ParcelFileDescriptor parcel = null;
String fileNameRequested = uri.getLastPathSegment();
String[] name=fileNameRequested.split("\\.");
String prefix=name[0];
String suffix=name[1];
// String path = getContext().getFilesDir().getAbsolutePath() + "/" + uri.getPath();
//String path=file:///android_asset/"+Consts.FILE_JAVASCRIPT+"
/*check if this is a javascript file*/
if(suffix.equalsIgnoreCase("js")){
InputStream is = null;
try {
is = getContext().getAssets().open("www/"+Consts.FILE_JAVASCRIPT);
} catch (IOException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
File file = stream2file(is,prefix,suffix);
try {
parcel = ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_ONLY);
} catch (FileNotFoundException e) {
Log.e("FileContentProvider", "uri " + uri.toString(), e);
}
}
return parcel;
}
/*converts an inputstream to a temp file*/
public File stream2file (InputStream in,String prefix,String suffix) {
File tempFile = null;
try {
tempFile = File.createTempFile(prefix, suffix);
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
tempFile.deleteOnExit();
FileOutputStream out = null;
try {
out = new FileOutputStream(tempFile);
} catch (FileNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
try {
IOUtils.copy(in, out);
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return tempFile;
}
#Override
public boolean onCreate() {
return true;
}
#Override
public int delete(Uri uri, String s, String[] as) {
throw new UnsupportedOperationException("Not supported by this provider");
}
#Override
public String getType(Uri uri) {
throw new UnsupportedOperationException("Not supported by this provider");
}
#Override
public Uri insert(Uri uri, ContentValues contentvalues) {
throw new UnsupportedOperationException("Not supported by this provider");
}
#Override
public Cursor query(Uri uri, String[] as, String s, String[] as1, String s1) {
throw new UnsupportedOperationException("Not supported by this provider");
}
#Override
public int update(Uri uri, ContentValues contentvalues, String s, String[] as) {
throw new UnsupportedOperationException("Not supported by this provider");
}
}
in the manifest i defined the provider:
<provider android:name="com.example.mypackage.FileContentProvider"
android:authorities="com.example.fileprovider"
/>
Here is the javascript o inject into the webview:
webView.loadUrl("javascript:(function() { "
+ "var script=document.createElement('script'); "
+ " script.setAttribute('type','text/javascript'); "
+ " script.setAttribute('src', 'content://com.example.fileprovider/myjavascriptfile.js'); "
/* + " script.onload = function(){ "
+ " test(); "
+ " }; "
*/ + "document.body.appendChild(script); "
+ "})();");
and here is the myjavascriptfile.js (as an example):
function changeBackground(color) {
document.body.style.backgroundColor = color;
}
Maybe you could have assets as 'html/javascript templates'. You could combine different of these text sources and string logic to compose your desired html to be loaded into the WebViewer. Then, you use .loadData instead of .loadUrl
I'm using it on my own and it seems to work pretty well.
Hope it helps!
With the following two conditions given:
minSdkVersion 21
targetSdkVersion 28
I am able to successfully load any local asset (js, png, css) via the following Java code
#Override
public WebResourceResponse shouldInterceptRequest(WebView view, WebResourceRequest request) {
Uri uri = request.getUrl();
if (uri.getHost().equals("assets")) {
try {
return new WebResourceResponse(
URLConnection.guessContentTypeFromName(uri.getPath()),
"utf-8",
MainActivity.this.getAssets().open(uri.toString().substring(15)));
} catch (IOException e) {
e.printStackTrace();
}
}
return null;
}
And in the HTML code I can use
<link rel="stylesheet" href="https://assets/material.min.css">
<script src="https://assets/material.min.js"></script>
<script src="https://assets/moment-with-locales.min.js"></script>
<img src="https://assets/stackoverflow.png">
In Java the following then also works (you'd also need to add a favicon.ico to the assets)
webView.loadUrl("https://assets/example.html");
Using https:// as the scheme allows me to load local assets from a page served via HTTPS without security issues due to mixed-content.
None of these require to be set:
webSettings.setMixedContentMode(WebSettings.MIXED_CONTENT_ALWAYS_ALLOW);
webSettings.setDomStorageEnabled(true);
webSettings.setAllowContentAccess(true);
webSettings.setAllowFileAccess(true);
webSettings.setAllowFileAccessFromFileURLs(true);
webSettings.setAllowUniversalAccessFromFileURLs(true);

Categories