Injecting javascript using LoadUrl fails in Android WebView for SDK 24+ - javascript

I am trying to inject a javascript namespace with function from App to Android WebView
NOTE: I do not have any control on the actual source being loaded on WebView apart from the below script. I am working in Xamarin so the Android code is C#
So there are two major components that I am using:
(function() {
console.log("Loading JS Bindings");
function addOnClickHandler () {
$(document).ready(function () {
if (window.InjectedNamespace) { // check InjectedNamespace namespace exists
var onClickHandler = window.InjectedNamespace.handleOnClick;
if (typeof onClickHandler === 'function') {
$(":button, a").each(function (idx) {
$(this).click(function() {
onClickHandler("click!");
});
});
} else {
console.error('missing onclick handler');
}
} else {
console.error('missing InjectedNamespace namespace');
}
});
}
})();
Above script which will be added by the third-party, which will attach click function to all <button> and <a> tags.
Below namespace will be injected from the App side
var InjectedNamespace={handleOnClick:function(a){Internal.handleOnClickEvent(a)}};
On App side I have this Javascript interface object
class JSInterfaceObject : Java.Lang.Object
{
readonly CustomWebView View;
string Data;
public JSInterfaceObject(CustomWebView view)
{
View = view;
}
[Export]
[JavascriptInterface]
// only methods exposed with annotation [JavascriptInterface] are exposed to javascript
public void handleOnClickEvent(string data)
{
Log.ForContext("TAG", TAG).Debug("handleOnClickEvent: " + data);
if(View.OnClick != null)
{
View.OnClick(data);
}
}
}
And this interface is added to webview using
AddJavascriptInterface(new JSInterfaceObject(this), "Internal");
And in OnPageStarted I am injecting the javascript using
webview.LoadUrl("javascript:"+"var IM={handleOnClick:function(a){Internal.handleOnClickEvent(a)}};");
This setup work fine for Android SDK < 24. But for Webview in SDK >=24 the script always errors out with missing InjectedNamespace namespace, which implies the LoadUrl in OnPageStarted failed!
This check is done in $(document).ready in the script.
I found this note in "Android 7.0 for Developers" which says
"Starting with apps targeting Android 7.0, the Javascript context will
be reset when a new page is loaded. Currently, the context is carried
over for the first page loaded in a new WebView instance.
Developers looking to inject Javascript into the WebView should
execute the script after the page has started to load."
So tried to add the javascript injection code after the page started loading but got the same error. Also tried using the WebView.EvaluateJavascript() but error persists.
The error disappers if I change my TargetSDK to <=23.

Related

How to invoke Eclipse RCP perspective ( Java Class method) from JavaScript code?

I would like to invoke or open Perspective(or Views) from JavaScript function.
I have an Eclipse RCP application, where I have embedded My HTML page. On button click from HTML page, i would like to open Eclipse RCP perspective or View.
I have tried as shown below from javascript function
function populateGeometryData(){
alert('Inside populateGeometryData() function !!!!!');
var geoMetryDataClass = Java.type("com.test.app.ui.view.PageView");
geoMetryDataClass.populateGemetryData("TestVal");
}
Where PageView is one of my perspective. But I got " Uncaught ReferenceError: Java is not defined" .
I think, this error is because of Chrome no longer supports NPAPI (technology required for Java applets). So, is there any other ways to invoke Java class method from JavaScript?
Below is how, I am launching my HTML page from Eclipse RCP code.
#Override
public void createPartControl(Composite parent) {
String user = ExecuteRestService.getLoggedInUser();
System.out.println("Login User "+user);
String html = "/Pages/index.html";
Bundle appBundle = FrameworkUtil.getBundle(this.getClass());
if(appBundle == null) {
System.out.println("bundle is null");
return;
}
URL url = FileLocator.find(appBundle, new Path(html), null);
try {
url = FileLocator.toFileURL(url);
} catch (IOException e) {
// TODO Auto-generated catch block#*
e.printStackTrace();
}
Browser browser = new Browser(parent, SWT.None);
String urlVal = url.toString()+"?LoginUser="+user;
browser.setUrl(urlVal);
}
I am looking some suggestions to call Java Class Method or to Invoke Eclipse RCP perspective from JavaScript function.
Thank you in advance.
UPDATE
As Greg suggested in comments section, using "BrowserFunction" we can call Java class method in Eclipse RCP from javascript if you are using SWT browser to launch your HTML page.
new BrowserFunction(browser, "populateGeometricData")
{
#Override
public Object function(Object[] objects)
{
if(objects.length == 0) {
System.out.println(" array is zero");
}else {
System.out.println("Call from Javascript"
+ objects.length);
System.out.println("Request Id "+ objects[0]);
}
return null;
}
};

UWP Webview - How to download a file generated in Javascript

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.

Accessing text files with Javascript in a chrome app

I would like to be able to edit and save text files in javascript, like the code below, but I have to be able to do it without using system.io, as this is a chrome app. Is there any way to be able to do this?
import System.IO;
var filePath = "data.txt";
function Start() {
if (!File.Exists(filePath)) {
CreateFile();
}
}
function CreateFile() {
var sw: StreamWriter = new StreamWriter(filePath);
sw.WriteLine("Hello World")
sw.Flush();
sw.Close();
print("Done");
}
While you are creating a chrome app, you can use chrome.fileSystem.
This snippet comes from the chrome app samples:
https://github.com/GoogleChrome/chrome-app-samples/tree/master/samples/text-editor
function openFile() {
chrome.fileSystem.chooseEntry(function (entry) {
if (chrome.runtime.lastError) {
showError(chrome.runtime.lastError.message);
return;
}
clearError();
setEntry(entry, false);
replaceDocContentsFromFileEntry();
});
}
For security reasons, JavaScript has no access to local files. It can only access the HTML document. To access external files, such as text files, you must use VBScript. Note that VBScript only works on IE and Edge browsers, and only when they are enabled in the browser's settings.

C# WPF Web Browser Control Security Error

I'm currently having trouble to nagivate to local file which have the index.html.
I had try to copied the URL link to firefox and google chrome and it worked. But when I'm trying to navigate to the URL link with C# Web Browser Control, it gave me a black screen with
Fatal: loading error (Security Error)
which I feel very weird.
Here is the example link: file://127.0.0.1/c$/Users/Marcus/Desktop/03%20Virtual%20Tour/dataranlang/index.html
Here is my code that used to called for the URL link in C# Web Browser Control:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.Reflection;
namespace Perak360
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
this.WB.Loaded += (s, e) =>
{
// get the underlying WebBrowser ActiveX object;
// this code depends on SHDocVw.dll COM interop assembly,
// generate SHDocVw.dll: "tlbimp.exe ieframe.dll",
// and add as a reference to the project
var activeX = this.WB.GetType().InvokeMember("ActiveXInstance",
BindingFlags.GetProperty | BindingFlags.Instance | BindingFlags.NonPublic,
null, this.WB, new object[] { }) as SHDocVw.WebBrowser;
// now we can handle previously inaccessible WB events
activeX.FileDownload += activeX_FileDownload;
};
this.Loaded += (s, e) =>
{
try
{
this.WB.Source = new Uri(new System.IO.FileInfo(#"C:\Users\Marcus Tan\Documents\Visual Studio 2013\Projects\Perak360\Perak360\Perakin360 Offline (Final)\index.html").FullName);
//this.WB.Navigate(Properties.Settings.Default.WebBrowserPath);
}
catch (Exception ex)
{
MessageBox.Show(ex.ToString());
}
};
}
void activeX_FileDownload(bool ActiveDocument, ref bool Cancel)
{
Cancel = true;
}
private void CloseWindow(object sender, TouchEventArgs e)
{
Application.Current.Shutdown();
}
}
}
I know that if i changed the URL link to file://C:/Users/Marcus%20Tan/Desktop/03%20Virtual%20Tour/dataranlang/index.html it may work, but there will be a javascript prompt out which ask user to enable the blocked content which I don't want it to be appear.
Any clue what wrong with my URL link or code which it gave me a black screen without the loading error screen? I had try to search online for solution and try to set the Internet Security setting, but none of it work.
Please guide me through this.

firebreath chrome extension: form saver

I have coded a browser helper object in C# for IE, and would like to wrap up that code to implement a chrome/firefox extension. My research led me to Firebreath. I am now loading a content_script file which will fire an onSubmit event and send a message to backgound.htm,js files which handles my firebreath plugin. However, sure enough, m_host now points to the background htm file rather than the original website (from which the user pressed submit button). I tried replacing the html code form the native plugin, but it is not working..
test setup:
content_script:
document.addEventListener('DOMContentLoaded', function () {
alert(document.forms.length);
for (var i = 0; i < document.forms.length; i++) {
document.forms[i].addEventListener("submit", function () {
var documentHTML = "<html>" + document.documentElement.innerHTML + "</html>";
chrome.runtime.sendMessage({ inputElement: documentHTML }, function (response) { }); }); } });
background.js:
chrome.runtime.onMessage.addListener(
function (request, sender, sendResponse) {
document.getElementById('plugin0').sethtml(request.inputElement); });
firebreath code:
void coolnewtestpluginAPI::setInnerHTML(const FB::variant& innerHTML)
{
m_host->getDOMDocument()->setInnerHTML(innerHTML.convert_cast<std::string>());
}
The native plugin is being called, and I can debug through it. "setInnerHTML()" returns successfully without any errors, but the html code is not being updated, and refers to the original background.htm file. Any ideas?
PS: I'm not that fluent in javascript, so I'd rather offload as much as possible to c# / c++
The only way to put an NPAPI/FireBreath plugin into the actual page is to inject the object tag into that page using javascript from the extension. Otherwise the plugin will know nothing about that page.

Categories