My requirements:
This should work in Android and iOS
I want to load a script (could be Javascript or Lua or any other scripting language) from a file (not necessarily over the network)
The script should run in a background thread and be able to post to the UI thread
I want to use the same script in iOS and Android, so that I can reuse the script code both in Android and iOS app
What are my options?
WebView Android with Javascript Enabled
SFWebView i think in iOS also support Javascript.
Not sure that you can use it in background thread as Android Webview will only runs on UI thread
Answer:
1. You MUST load the HTML into string:
private String readHtml(String remoteUrl) {
String out = "";
BufferedReader in = null;
try {
URL url = new URL(remoteUrl);
in = new BufferedReader(new InputStreamReader(url.openStream()));
String str;
while ((str = in.readLine()) != null) {
out += str;
}
} catch (MalformedURLException e) {
} catch (IOException e) {
} finally {
if (in != null) {
try {
in.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
return out;
}
Load WebView with base URL:
String html = readHtml("Your Web URL");
mWebView.loadDataWithBaseURL("file:///android_asset/", html, "text/html", "utf-8", "");
In this particular case you should have all .js files you want to use on the page to reside somewhere under "assets" folder of project. For example:
/MyProject/assets/jquery.min.js
In your remote html page you have to load .js and .css files that reside in your application like:
the same applies to all other local resources like images, etc. their path has to start with
file:///android_asset/
A WebView would first load the raw HTML you have provided as string, then pick .js, .css and other local resourses and then would load remote content.
In android, While loading the page into webview. Intercept the script call.
public WebResourceResponse shouldInterceptRequest(WebView view, String url) {
if ( url.endsWith("script.js") ) {
AssetFileDescriptor fileDescriptor = assetManager.openFd("script.js");
FileInputStream stream = fileDescriptor.createInputStream();
return new WebResourceResponse("text/javascript","UTF-8",stream);
}
}
Webview runs on ui thread. I am not sure that there is way to load webresources from background threads.
Related
I have a Java application that as part of it's output creates an html report ,then opens the html reports in users browser. On Firefox and Google Chrome this works but on Safari it opens the report as if Javascript was not enabled, even though it is. However if you reopen the report by clicking on a link from another webpage (which lists all reports) then it opens fine in Safari.
What do I need to do to trigger Safari to open the report with Javascript enabled.
Console shows some errors, but I dont understand them
This is related issue https://apple.stackexchange.com/questions/361245/safari-kcferrordomaincfnetwork-error-1-on-local-html-files but doesn't provide a satisafactory answer.
Actually the answer here https://apple.stackexchange.com/questions/366448/safari-giving-kcferrordomaincfnetwork-error-303-when-visiting-a-site about removing my site from Preferecnes:Privacy works but that is no good because the problem occurs on new computer after running the program only a few times so would have to continually do it.
You open the file locally. Browsers usually restrict local file processing for security reasons. You must "Disable local file restrictions" in Safari in the "Developer" menu for making this possible. I am not sure, but it might be necessary to do this each time Safari opens such a file via your application. Embedding all external resources might also help, but I am not sure.
Opening the remote URL should always work. So this would be the best option. As an alternative you could serve the file to the browser via an embedded HTTP server in your application.
Edit: i just saw the previous answer proposed the same workaround, but i think copy paste code is always delicious. As for your problem, i verified this is occuring on safari and nothing else, and for me this fix worked.
Edit 2: According to this answer, it is a bug in Safari that it works when you open it via hyperlink. Without changing the developer settings in every users browser, it won't be working except on a local server. I'm sorry but it seems there is no other in-code workaround except what i already provided.
If you want to open with scripts at any cost, you can to the following workaround:
Write a minimalistic server socket (or copy paste my code):
Index.java:
package index;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Paths;
public class Index extends Base {
public byte[] buildResponseBody(String resource) {
try {
System.out.println(resource);
if(resource.contains("?")) return "<!DOCTYPE HTML><html><h1>Error 404: Page not found</h1></html>".getBytes(StandardCharsets.US_ASCII);
return Files.readAllBytes(Paths.get(resource));
} catch (Exception e) {
return "<!DOCTYPE HTML><html><h1>Error 404: Page not found</h1></html>".getBytes(StandardCharsets.US_ASCII);
}
}
public static void main(String[] args) throws IOException {
new Index().loop(80);
}
}
Base.java:
package index;
import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public abstract class Base {
public abstract byte[] buildResponseBody(String resource);
String receiveRequest(BufferedReader reader) throws IOException {
final Pattern getLinePattern = Pattern.compile("(?i)GET\\s+/(.*?)\\s+HTTP/1\\.[01]");
String resource = null;
try {
for (String line = reader.readLine(); !line.isEmpty(); line = reader.readLine()) {
Matcher matcher = getLinePattern.matcher(line);
if (matcher.matches()) {
resource = matcher.group(1);
}
}
} catch (NullPointerException e) {
return null;
}
return resource;
}
public void sendResponse(String resource, OutputStream output, PrintWriter writer) throws IOException {
byte[] responseBody = buildResponseBody(resource);
if (responseBody == null) {
writer.println("HTTP/1.0 404 Not found");
responseBody = "Not found".getBytes();
} else
writer.println("HTTP/1.0 200 OK");
writer.println("Server: " + getClass().getSimpleName());
writer.println("Content-Length: " + responseBody.length);
writer.println();
writer.flush();
output.write(responseBody);
}
public void loop(int port) throws IOException {
try (ServerSocket serverSocket = new ServerSocket(port)) {
while (true)
try (Socket socket = serverSocket.accept();
InputStream input = socket.getInputStream();
BufferedReader reader = new BufferedReader(new InputStreamReader(input));
OutputStream output = socket.getOutputStream();
PrintWriter writer = new PrintWriter(new OutputStreamWriter(output))) {
sendResponse(receiveRequest(reader), output, writer);
}
}
}
}
This enables opening a page at 127.0.0.1:80. If you now make sure the document is accessable to your application, you can make it open a browser window at your localhost:80/file.html. Since it now runs over an actual website not just a file in safari, the scripts should theoretically be working. (At least mine are in this code)
You might want to improve security and remove some bugs of this code. I hope i could help.
I'm developing an angular website which is loading in an app from a WebView, and there is only one of the links in it that has to be opened outside of the app (external browser)
I need a way to handle this from JavaScript not putting extra work to the android side.
and i have already tried some ways including:
window.open("url","_system")
(navigator as any).app.loadUrl("http://google.com", {openExternal : true});
Well, there is no such thing
instead it must be handled from android application code. you can add a parameter to the url when u need it to open in external browser, ( here it is external=true ) and then check for that parameter in your webview url loading as below:
webView.setWebViewClient(new WebViewClient(){
#RequiresApi(api = Build.VERSION_CODES.N)
#Override
public boolean shouldOverrideUrlLoading(WebView view, WebResourceRequest request) {
if((String.valueOf(request.getUrl())).contains("external=true")) {
Intent intent = new Intent(Intent.ACTION_VIEW, request.getUrl());
view.getContext().startActivity(intent);
return true;
} else {
view.loadUrl(String.valueOf(request.getUrl()));
}
return true;
}
});
I'm developing an application in React Native which uses some windows native components. One of those components is a WebView.
I'm showing a website via this WebView and this website records the camera video and prompts the user to download the file (it's a file created from a Blob and URL.createObjectURL(blob)). However, the WebView does not open any folder picker or file picker, and the user cannot save the file.
Here is my WebView code (remember it has some properties of React Native):
using ReactNative.UIManager;
using ReactNative.UIManager.Annotations;
using System;
using System.Reflection;
using Windows.UI.Xaml.Controls;
namespace ListProject.CustomModules
{
public class PremissionWebviewViewManager : SimpleViewManager<WebView>
{
private Uri sourceUri;
private WebView thiswv;
public override string Name
{
get
{
return "PermissionWebviewViewManager";
}
}
[ReactProp("sourceUri")]
public void SetSource(WebView view, String source)
{
this.sourceUri = new Uri(source);
thiswv.Navigate(this.sourceUri);
}
protected override WebView CreateViewInstance(ThemedReactContext reactContext)
{
thiswv = new WebView();
thiswv.PermissionRequested += HandlePermissionRequested;
// thiswv.UnviewableContentIdentified += HandleDownloadContent; <--- I thought this could help, but I came up with nothing
return thiswv;
}
private void HandlePermissionRequested(WebView sender, WebViewPermissionRequestedEventArgs args)
{
args.PermissionRequest.Allow();
}
private void HandleDownloadContent(WebView sender, WebViewUnviewableContentIdentifiedEventArgs args)
{
// Windows.System.Launcher.LaunchUriAsync(args.Uri);
}
}
}
Here is the React Native component:
render() {
return (
<PermissionWebview
style={{flex: 1}}
mediaPlaybackRequiresUserAction={false}
domStorageEnabled={true}
allowsInlineMediaPlayback={true}
source={{uri: this.website}}
sourceUri={this.website}
allowFileAccessFromFileURLs={true}
allowUniversalAccessFromFileURLs={true}
/>
);
}
And here the script used in JS on the website part to trigger the download:
var blob = new Blob(recordedVideo, {type: 'video/webm'});
var videoUrl = URL.createObjectURL(blob);
var anchorTag = $('<a class="download_link"></a>');
anchorTag.attr('href', videoUrl);
anchorTag.attr('target', '_blank');
anchorTag.attr('download', 'video.webm');
// Add the anchor tag to DOM
$('body').append(anchorTag);
// We use DOM element and not JQ element to trigger DOM events (not JQ ones)
anchorTag[0].click();
// Remove tag once clicked and event triggered
anchorTag.remove();
The Windows app has all file capabilites granted in the manifest (though the user is not asked to let the app access the file system), and in a native Edge browser it works - so it's not an Edge API limitation (WebView uses Edge engine in UWP W10).
How can I achieve so the download window is triggered and the file can be downloaded on the client?
Thank you.
Windows won't let my WebView_ScriptNotify Event receive a call if the html is loaded from ms-appdata.
I'm aware that I can use the ms-appx-web protocol to load such a file from my app bundle, but this is no option because the data to show are downloaded after install of the app.
I also can't just use webView.navigateToString because this won't include the referenced libraries in the html file.
Currently I'm trying something like this in my Class.xaml.cs
WebView webView = new WebView();
webView.ScriptNotify += WebView_ScriptNotify;
Uri navigationUri = new Uri(#"ms-appdata:///local/index.html");
webView.Navigate(navigationUri);
and
private void WebView_ScriptNotify(object sender, NotifyEventArgs e)
{
System.Diagnostics.Debug.WriteLine("ScriptNotifyValue: " + e.Value);
//I want to do the magic here, but this will never be called
}
in the html file is
<div id="content">
<div class="btn" onClick="window.external.notify('hello world');"</div>
</div>
Furthermore, it's no option to use InvokeScript(), because I don't know when the event must be fired and the values for it.
Yet it's mandatory to use files from ms-appdata.
Do you know a solution for this?
Even an alternative workaroung would amaze me.
Ref Script notify changes in XAML:
For content to be able to send notifications the following conditions apply:
The source of the page should be from the local system via NavigateToString(), NavigateToStream() or ms-appx-web:///
Or
The source of the page is delivered via https:// and the site domain name is listed in the app content URI’s section of the package manifest.
So to solve this issue, we can use WebView.NavigateToLocalStreamUri method with the protocol ms-local-stream://, rather than ms-appdata://. For example:
public sealed partial class MainPage : Page
{
public MainPage()
{
this.InitializeComponent();
}
protected override void OnNavigatedTo(NavigationEventArgs e)
{
// The 'Host' part of the URI for the ms-local-stream protocol needs to be a combination of the package name
// and an application-defined key, which identifies the specific resolver, in this case 'MyTag'.
Uri url = webView.BuildLocalStreamUri("MyTag", "index.html");
StreamUriWinRTResolver myResolver = new StreamUriWinRTResolver();
// Pass the resolver object to the navigate call.
webView.NavigateToLocalStreamUri(url, myResolver);
}
private void webView_ScriptNotify(object sender, NotifyEventArgs e)
{
System.Diagnostics.Debug.WriteLine("ScriptNotifyValue: " + e.Value);
}
}
public sealed class StreamUriWinRTResolver : IUriToStreamResolver
{
public IAsyncOperation<IInputStream> UriToStreamAsync(Uri uri)
{
if (uri == null)
{
throw new Exception();
}
string path = uri.AbsolutePath;
// Because of the signature of the this method, it can't use await, so we
// call into a seperate helper method that can use the C# await pattern.
return GetContent(path).AsAsyncOperation();
}
private async Task<IInputStream> GetContent(string path)
{
// We use app's local folder as the source
try
{
Uri localUri = new Uri("ms-appdata:///local" + path);
StorageFile f = await StorageFile.GetFileFromApplicationUriAsync(localUri);
IRandomAccessStream stream = await f.OpenAsync(FileAccessMode.Read);
return stream;
}
catch (Exception) { throw new Exception("Invalid path"); }
}
}
For more info, please see Remarks and Examples in WebView.NavigateToLocalStreamUri method and also Custom URI resolving in What’s new in WebView in Windows 8.1. Besides, there is also a WebView control (XAML) sample on GitHub.
So I'm building an app with a lot of web content I plan to release it using Phone Gap build but will host all the content online and will link to it. I was wondering if there is a way that the web pages can be downloaded when there is an active internet connection for offline use and when there is a connection again for the data to be refreshed preferably when the user is using a wifi connection. The site will mostly be in html, js, and php. I will be hosting with bluehost
Is there any way of doing this? Thanks in advance! Littleswany!
PhoneGap apps ARE downloaded to the device, when they are downloaded from the store. They are basically a wrapper around an index.html file, but the app is actually programmed in JavaScript, which is responsible for creating and displaying views etc. The only time you need to check for an internet connection is when you are communicating with your back end (PHP)... If the ajax request fails, the best solution is to provide the user with a button/link to try again when they have regained their internet connection, or set a timer which fires intermittently to keep trying again... NEVER use a while(true) loop in your Phone Gap app - it will just hang.
I am not familiar with java, but i think i can provide the logic to get the job done.
You want to do an infinite loop that checks if the user is on wifi. Then if true, use wget, rsync, or scp to download the website. Something like this.:
while (true){
// do an if statement that checks if user is on wifi. Then do a then statement that uses rsync or wget.
}
Info on how to nest if statements in while loops in java: java loop, if else
I do not know if wget, rsync, or scp can be ran from java. You'll need to look more into it or write your own alternative function to do it. Something like:
function download_file() {
var url = "http://www.example.com/file.doc"
window.location = url;
}
You should be able to do it from your java like this:
String whatToRun = "/usr/local/bin/wget http://insitu.fruitfly.org/insitu_image_storage/img_dir_38/insitu38795.jpe";
Sources:
1. What is the equivalent of wget in javascript to download a file from a given url?
2. Call a command in terminal using Java (OSX)
First Create an Connection filter class
public class Connection_Status{
private static ConnectivityManager connectivityManager;
static boolean connected = false;
public static Boolean isOnline(Context ctx) {
try {
connectivityManager = (ConnectivityManager) ctx.getSystemService(Context.CONNECTIVITY_SERVICE);
NetworkInfo networkInfo = connectivityManager.getActiveNetworkInfo();
connected = networkInfo != null && networkInfo.isAvailable()&& networkInfo.isConnected();
return connected;
} catch (Exception e) {
System.out.println("CheckConnectivity Exception: " + e.getMessage());
}
return connected;
}
}
And in your Main class
public class Main extends Activity{
private WebView mWebView;
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mWebView = (WebView) findViewById(R.id.webview);
mWebView.getSettings().setJavaScriptEnabled(true);
mWebView.getSettings().setBuiltInZoomControls(true);
if(Connection_Status.isOnline(Main.this)){
HttpClient httpclient = new DefaultHttpClient(); // Create HTTP Client
HttpGet httpget = new HttpGet("http://yoururl.com"); // Set the action you want to do
HttpResponse response = httpclient.execute(httpget); // Executeit
HttpEntity entity = response.getEntity();
InputStream is = entity.getContent(); // Create an InputStream with the response
BufferedReader reader = new BufferedReader(new InputStreamReader(is, "iso-8859-1"), 8);
StringBuilder sb = new StringBuilder();
String line = null;
while ((line = reader.readLine()) != null) // Read line by line
sb.append(line + "\n");
String resString = sb.toString(); //
is.close(); // Close the stream
}
}
}
Or you can use cache on it e.g
mWebView.getSettings().setAppCacheMaxSize(1024*1024*8);
mWebView.getSettings().setAppCachePath(""+this.getCacheDir());
mWebView.getSettings().setAppCacheEnabled(true);
mWebView.getSettings().setCacheMode(WebSettings.LOAD_DEFAULT);
Don't forget to add the following permissions
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> <!-- for the connection status-->
Sources:
https://stackoverflow.com/a/6503817/1309629