C# WPF Web Browser Control Security Error - javascript

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.

Related

Is there a way to create bi-directional communication between a shell app and a webview in .NET 6 MAUI?

Recently I've had the assignment to create a bi-directional interop bridge between a shell app and a webpage in .NET MAUI. Not finding any way to solve this I had the idea of creating it in Xamarin.Forms first seeing as MAUI is a continuation on it.
After having created this app, I've tried to convert it over to MAUI using Microsoft's instructions on the dotnet/maui github wiki.
The main problem i'm encountering right now is that I've been using extensions on Android's WebViewRenderer, WebViewClient and Java.Lang.Object to be able to send and receive javascript to and from the WebView.
public class ExtendedWebViewRenderer : WebViewRenderer
{
private const String JavascriptFunction = "function invokeCSharpAction(data){jsBridge.invokeAction(data);}";
public ExtendedWebViewRenderer(Context context) : base(context)
{
}
protected override void OnElementChanged(ElementChangedEventArgs<WebView> e)
{
base.OnElementChanged(e);
if (e.OldElement != null)
{
Control.RemoveJavascriptInterface("jsBridge");
((ExtendedWebView)Element).Cleanup();
}
if (e.NewElement != null)
{
Control.SetWebViewClient(new JavascriptWebViewClient($"javascript: {JavascriptFunction}"));
Control.AddJavascriptInterface(new JsBridge(this), "jsBridge");
}
}
}
public class JavascriptWebViewClient : WebViewClient
{
private readonly String _javascript;
public JavascriptWebViewClient(String javascript)
{
_javascript = javascript;
}
public override void OnPageFinished(WebView view, String url)
{
base.OnPageFinished(view, url);
view.EvaluateJavascript(_javascript, null);
}
}
public class JsBridge : Java.Lang.Object
{
private readonly WeakReference<ExtendedWebViewRenderer> _extendedWebViewMainRenderer;
public JsBridge(ExtendedWebViewRenderer extendedRenderer)
{
_extendedWebViewMainRenderer = new WeakReference<ExtendedWebViewRenderer>(extendedRenderer);
}
[JavascriptInterface]
[Export("invokeAction")]
public void InvokeAction(String data)
{
if (_extendedWebViewMainRenderer != null && _extendedWebViewMainRenderer.TryGetTarget(out var extendedRenderer))
{
((ExtendedWebView)extendedRenderer.Element).InvokeAction(data);
}
}
}
All three of these are either not available right now, or will not be implemented in MAUI, since a lot of platform dependent code has been automated now. Which leaves me with the problem that I can't seem to figure out how to change my current code to accomplish the same thing in MAUI.
Seeing as MAUI is currently not even fully released, I was wondering if this is currently just not possible or if there is some workaround to make it possible.
Any help would be greatly appreciated.
Calling C# from a webview is actually extremely simple. Just navigate and then intercept that in c#.
the xaml:
<WebView WidthRequest="400" HeightRequest="400" Navigating="WebView1_Navigating">
<WebView.Source>`enter code here`
<HtmlWebViewSource>
<HtmlWebViewSource.Html>
<![CDATA[
<HTML>
<script>
function callCsharp(){
window.location.href = 'http://poc.MyFunction?name=john&country=DK';
}
</script>
<BODY
A link that triggers C#
<br>
<button onclick="callCsharp()" type="button">A button calling javascript</button>
</BODY>
</HTML>
]]>
</HtmlWebViewSource.Html>
</HtmlWebViewSource>
</WebView.Source>
The C#:
private async void WebView1_Navigating(object sender, WebNavigatingEventArgs e)
{
var urlParts = e.Url.Split(".");
if (urlParts[0].ToLower().Equals("http://poc"))
{
var funcToCall = urlParts[1].Split("?");
var methodName = funcToCall[0];
var funcParams = funcToCall[1];
Debug.WriteLine("Calling " + methodName);
// prevent the navigation to complete
e.Cancel = true;
// TODO smart parsing and type casting of parameters and then some reflection magic
}
}
MAUI's default WebView has the Eval and EvaluateJavaScriptAsync functions to call JavaScript code from C#:
Eval just executes the script string you pass in a fire-and-forget way.
EvaluateJavaScriptAsync needs to be awaited but also returns a string with a stringified result of the data that the script returned.
If you want to use callback/bridge methods to automatically receive data from the JavaScript side without any input from the C# side of the app, you will have to extend the default per-platform renderers to add that functionality. The good news is that there is an official tutorial on how to do it for Xamarin Forms at Customizing a WebView which is almost straightforward to port to .NET MAUI - you only have to change how renderers are registered.

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.

When invoking a URL, how to wait for all of its contents to completely load?

Background:
I have an html page that has a few DIVs and tables that are already in the document DOM. when the url of this page is invoked, a JavaScript code fills and populates the contents of this page. This may taake 2~3 seconds, but eventually gets done nicely.
Problem:
I would like to invoke the URL of this page in C#, to get its contents in a nice html string.
I have tried many tricks that I learned from SO. However, all of them only return the bare-bone structure of the page without any content. These include:
Using WebBrowser
Using WebClient-1
Using WebClient-2
Using WebRequest
Now my question is that is there a nice way of waiting for all the contents to load when invoking a URL?
some options to me seem like to be along:
Is there a way to tell one of these class to wait a certain amount of time before downloading the contents?, or:
Is there another Event on WebBrowser class that I can hook so that it waits for all the content to load?
Thanks in advance.
Edit 3:
Tried this other solution using HttpWebRequests, but got this error:
'HttpWebRequest.HttpWebRequest()' is obsolete: 'This API supports the .NET Framework infrastructure and is not intended to be used directly from your code.'
Edit 2:
Even this other WebBrowser solution does not work!!
It raises the JS error "object does not support "CallServerSideCode"
Edit 1: as Austin asked, I am including some of the code that I have tried:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.Diagnostics;
using System.Net;
using System.Net.Http;
using System.Threading;
using System.IO;
namespace WindowsFormsApp1
{
public partial class Form1 : Form
{
WebBrowser wb = new WebBrowser();
public Form1()
{
InitializeComponent();
wb.DocumentCompleted += new WebBrowserDocumentCompletedEventHandler(wb_DocumentCompleted);
}
private void button1_Click(object sender, EventArgs e)
{
using(var webc = new WebClient())
{
var json = webc.DownloadString(textBox1.Text);
Debug.WriteLine("WebClient: \r\n" + json.ToString() + " \r\n\r\n\r\n");
}
}
private void button2_Click(object sender, EventArgs e)
{
WebRequest request = WebRequest.Create(textBox1.Text);
// If required by the server, set the credentials.
request.Credentials = CredentialCache.DefaultCredentials;
// Get the response.
Thread.Sleep(5000);
WebResponse response = request.GetResponse();
// Display the status.
Console.WriteLine(((HttpWebResponse)response).StatusDescription);
// Get the stream containing content returned by the server.
Stream dataStream = response.GetResponseStream();
// Open the stream using a StreamReader for easy access.
StreamReader reader = new StreamReader(dataStream);
// Read the content.
string responseFromServer = reader.ReadToEnd();
// Display the content.
Console.WriteLine("Web Request: \r\n" + responseFromServer + " \r\n\r\n\r\n");
// Clean up the streams and the response.
reader.Close();
response.Close();
}
private void button3_Click(object sender, EventArgs e)
{
wb.Navigate(new Uri(textBox1.Text));
}
private void wb_DocumentCompleted(object sender, WebBrowserDocumentCompletedEventArgs e)
{
string page = wb.DocumentText;
Console.WriteLine("Web Browser: \r\n" + page + " \r\n\r\n\r\n");
}
}

C# Webbrowser Programmatically Close JS Confirm Box

while using webbrowser control, I need to programmatically auto close a javascript confirm box.
I used below user32.dll approach and it is working fine on OS which are based english language.
[DllImport("user32.dll", CharSet = CharSet.Auto)]
static extern IntPtr SendMessage(IntPtr hWnd, UInt32 Msg, IntPtr wParam, IntPtr lParam);
But if the computer running non-english OS, it is not working fine as I am using "OK" as text in above method call.
One approach which I suppose can work is I should detect OS language and then use translated "OK" text to use above method.
Here my question is can I change language of the current thread and so webbrowser control so that it show confirm box in English language? This way it would be easy and fast solution in my opinion.
Please suggest your solutions. Thanks in advance.
I am using similar approach in my code however these solutions are working for English language software only. I am actually looking for some generic solution that can run on non-english OS as well.
A possible solution consists in injecting and immediately calling a Javascript function that hijacks the original confirm function:
function hijackConfirm(){
alert('yep!');
window.oldConfirm = window.confirm;
window.confirm = function(){ return true };
}
This is an example in WPF application with the standard WPF WebBrowser control, I'm quite confident that everything I do here can be adjusted to fit the WinForm control (since the underlying ActiveX is the same).
I have a UserControl that acts as an adapter of the WebBrowser, here is the XAML:
<UserControl x:Class="WebBrowserExample.WebBrowserAdapter"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<Grid>
<WebBrowser x:Name="WebBrowserControl"></WebBrowser>
</Grid>
</UserControl>
First, in the WebBrowserAdapter class, you need a method to inject a javascript function in the current HTML document:
public void InjectScript(String scriptText)
{
HTMLDocument htmlDocument = (HTMLDocument)WebBrowserControl.Document;
var headElements = htmlDocument.getElementsByTagName("head");
if (headElements.length == 0)
{
throw new IndexOutOfRangeException("No element with tag 'head' has been found in the document");
}
var headElement = headElements.item(0);
IHTMLScriptElement script = (IHTMLScriptElement)htmlDocument.createElement("script");
script.text = scriptText;
headElement.AppendChild(script);
}
then you call InjectScript, when needed, whenever a document completes to load:
void WebBrowserAdapter_Loaded(object sender, RoutedEventArgs e)
{
WebBrowserControl.LoadCompleted += WebBrowserControl_LoadCompleted;
WebBrowserControl.Navigate("http://localhost:9080/console/page.html");
}
void WebBrowserControl_LoadCompleted(object sender, NavigationEventArgs e)
{
//HookHTMLElements();
String script =
#" function hijackConfirm(){
alert('yep!');
window.oldConfirm = window.confirm;
window.confirm = function(){ return true };
}";
InjectScript(script);
WebBrowserControl.InvokeScript("hijackConfirm");
}
Here I navigate to http://localhost:9080/console/page.html, which is a test page hosted on my system. This works well in this simple scenario. If you find this could apply to you, you may need to tweak a little bit the code. In order to compile the code, you have to add Microsoft.mshtml in the project references
EDIT: WinForm version
To make it work, you have to use the IE 11 engine in your application. Follow the instructions found here to set it
I just tried a WinForm version of this and it works with some minor changes. Here is the code of a form that has a WebBrowser control as one of its children:
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
this.Load += Form1_Load;
}
void Form1_Load(object sender, EventArgs e)
{
webBrowserControl.Navigate("file:///C:/Temp/page.html");
webBrowserControl.Navigated += webBrowserControl_Navigated;
}
void webBrowserControl_Navigated(object sender, WebBrowserNavigatedEventArgs e)
{
InjectConfirmHijack();
}
private void InjectConfirmHijack()
{
String script =
#" function hijackConfirm(){
alert('yep!');
window.oldConfirm = window.confirm;
window.confirm = function(){ return true };
}";
InjectScript(script);
webBrowserControl.Document.InvokeScript("hijackConfirm");
}
public void InjectScript(String scriptText)
{
//mshtml.HTMLDocument htmlDocument = (mshtml.IHTMLDocument) webBrowserControl.Document.get;
var headElements = webBrowserControl.Document.GetElementsByTagName("head");
if (headElements.Count == 0)
{
throw new IndexOutOfRangeException("No element with tag 'head' has been found in the document");
}
var headElement = headElements[0];
var script = webBrowserControl.Document.CreateElement("script");
script.InnerHtml = scriptText;
headElement.AppendChild(script);
}
}

Categories