Why code scheduled via setTimeout method in WebBrowser control is not invoked - c#

using System;
using System.Runtime.InteropServices;
using System.Windows.Forms;
public static class Program
{
[STAThread]
public static void Main()
{
using (var browser = new WebBrowser())
{
browser.Navigate(string.Empty);
browser.Document.InvokeScript("execScript", new object[] { "function set_obj(obj) { window.obj = obj }" });
browser.Document.InvokeScript("execScript", new object[] { "function say_hello() { window.obj.WriteLine('Hello world') }" });
browser.Document.InvokeScript("set_obj", new object[] { new Obj() });
browser.Document.InvokeScript("say_hello");
browser.Document.InvokeScript("setTimeout", new object[] { "say_hello()", 100 });
Console.ReadKey();
}
}
}
[ComVisible(true)]
public sealed class Obj
{
public void WriteLine(string message)
{
Console.WriteLine(message);
}
}
An immediate invocation of the method say_hello works fine, but when I postpone it using setTimeout, it is not invoked. Why? Is there any workaround?

As user #controlflow pointed, I need a message loop in my application to make setTimeout work. Adding the following line helps:
Application.Run(new Form { Controls = { browser }, WindowState = FormWindowState.Minimized, ShowInTaskbar = false });

Don't put the parentheses after say_hello, because you're not trying to call it there, but pass it as a delegate to a function. So try:
browser.Document.InvokeScript("setTimeout", new object[] { "say_hello", 100 });
Also, are there any errors in the console?
Update:
Try:
browser.Document.InvokeScript("setTimeout(say_hello, 100);");
Also try:
browser.Document.InvokeScript("setTimeout", new object[] { "say_hello", "100" });
Whatever the issue is, there's probably a JavaScript error being swallowed somewhere. Try to write out the rendered markup and script and run it in a normal web page in browser.

You should change the following line
browser.Document.InvokeScript("say_hello");
to
browser.Document.InvokeScript("say_hello()");
It throws a javascript exception, and probably it's the reason for the next command not to execute.

Related

CefSharp how to rename and embed BrowserSubProcess.exe

iam quite desperate here. I couldn't find any example code for this in C#.
I want to rename BrowserSubProcess.exe and i want it to embed my main exe, if possible.
I am aware of this solution;
https://github.com/cefsharp/CefSharp/issues/1149#issuecomment-225547869
Rename CefSharp.BrowserSubprocess winforms
but i couldn't implemented it. I need sample program or code to understand. I hope #amaitland will see this and helps me.
I embed the BrowserSubProcess Program.cs to my Program.cs so it is embedded now.
static class Program
{
/// <summary>
/// The main entry point for the application.
/// </summary>
[STAThread]
static void Main(string[] args)
{
if (args.Count() < 5)
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new LoginForm());
}
else
{
MyBrowserSubProcess(args);
}
}
static int MyBrowserSubProcess(string[] args)
{
Debug.WriteLine("BrowserSubprocess starting up with command line: " + String.Join("\n", args));
SubProcess.EnableHighDPISupport();
int result;
var type = args.GetArgumentValue(CefSharpArguments.SubProcessTypeArgument);
var parentProcessId = -1;
// The Crashpad Handler doesn't have any HostProcessIdArgument, so we must not try to
// parse it lest we want an ArgumentNullException.
if (type != "crashpad-handler")
{
parentProcessId = int.Parse(args.GetArgumentValue(CefSharpArguments.HostProcessIdArgument));
if (args.HasArgument(CefSharpArguments.ExitIfParentProcessClosed))
{
Task.Factory.StartNew(() => AwaitParentProcessExit(parentProcessId), TaskCreationOptions.LongRunning);
}
}
// Use our custom subProcess provides features like EvaluateJavascript
if (type == "renderer")
{
var wcfEnabled = args.HasArgument(CefSharpArguments.WcfEnabledArgument);
var subProcess = wcfEnabled ? new WcfEnabledSubProcess(parentProcessId, args) : new SubProcess(args);
using (subProcess)
{
result = subProcess.Run();
}
}
else
{
result = SubProcess.ExecuteProcess();
}
Debug.WriteLine("BrowserSubprocess shutting down.");
return result;
}
private static async void AwaitParentProcessExit(int parentProcessId)
{
try
{
var parentProcess = Process.GetProcessById(parentProcessId);
parentProcess.WaitForExit();
}
catch (Exception e)
{
//main process probably died already
Debug.WriteLine(e);
}
await Task.Delay(1000); //wait a bit before exiting
Debug.WriteLine("BrowserSubprocess shutting down forcibly.");
Environment.Exit(0);
}
}
And my BrowserSubprocessPath is my main exe.
settings.BrowserSubprocessPath = System.AppDomain.CurrentDomain.FriendlyName;
I finally managed to rename this sub process! Haven't found any solution how to do it through the CefSharp API, but found my own worked solution.
So, In your code that uses CefSharp add one setting to the Cef Settings, before Cef.Initialize()
using CefSharp;
using CefSharp.Wpf;
using System.IO;
using System.Reflection;
using System.Windows;
public App()
{
var settings = new CefSettings
{
BrowserSubprocessPath = Path.Combine(GetAppPath(), $#"runtimes\win-x64\native{ GetAppName() }.exe")
};
Cef.InitializeAsync(settings);
}
private static string GetAppPath()
{
return new FileInfo(Assembly.GetExecutingAssembly().Location).DirectoryName;
}
private static string GetAppName()
{
return Assembly.GetExecutingAssembly().GetName().Name;
}
After this go to the bin\Debug\net6.0-windows\runtimes\win-x64\native\ and rename CefSharp.BrowserSubprocess.exe to Name you want to use.
Done. Now it will use this file with custom name you need.
P.S. For the auto name set you can always use Post-Build event with command to rename the file after project built and set the name same as your assembly name. I use this approach for my needs.

WPF Browser throws error

I am calling javascript functions from C# using the WPF variant of the browser control via InvokeScript.
I can call my function once without any problems. But when I call it a second time, it throws the following error :
Unknown name. (Exception from HRESULT: 0x80020006
(DISP_E_UNKNOWNNAME))
The Code I am using is the following :
this.browser.LoadCompleted += (sender, args) =>
{
this.browser.InvokeScript("WriteFromExternal", new object[] { "firstCall" }); // works
this.browser.InvokeScript("WriteFromExternal", new object[] { "secondCall" }); // throws error
};
The javascript function is :
function WriteFromExternal(message) {
document.write("Message : " + message);
}
I can call C# functions from the page via javascript just fine and invoke from C#, just can't invoke a second time. Regardless of what function I call.
I do not understand why it would fail the second time.
Thank you
Edit :
Did the following test (javascript) :
function pageLoaded() {
window.external.tick();
window.external.tick();
window.external.tick();
}
window.onload = pageLoaded;
function WriteFromExternal(message) {
document.write("Message : " + message);
}
And this is the C# side :
private int i = 0;
public void tick()
{
invoke("WriteFromExternal", new object[] { "ticked"+ i++ });
}
public static void invoke(string method, object[] parameters)
{
mainInterface.browser.InvokeScript(method, parameters);
}
And still throws the same error (after the first call), this suggests that it does not matter from where it is called, invoking the function from C# will throw this error if done more than once.
I assume you did the same as me and put your scripts in the body. For some reason when you call document.write from wpf it completely overwrites the document. If instead of using document.write you append a child it works fine. So change your JavaScript function to be:
window.WriteFromExternal = function (message) {
var d = document.createElement("div")
d.innerHTML= "Message : " + message;
document.body.appendChild(d);
}
// call from c#
WriteFromExternal("test")
It's been a while since I did something similar, but from what I remember your code looks correct. However, I do remember using a slightly different pattern in my project. Instead of delegating back to a JS method on the page I would make my ScriptingHost methods return values
EX:
C#:
public string tick()
{
return "some stuff";
}
var msg = window.external.tick();
document.write(msg);
If you have more complex objects than simple strings you can serialize them to JSON and parse them into an object on the JS side.
var jsonObj = JSON.parse(window.external.someMethod());
Not sure if you have the luxury of being able to change your method signatures in your scripting object, but it's at least an alternative approach.
Also, in your current implementation, have you tried to do something other than document.write? Do you get the same error if you display an alert box?

Application.Quit() doesn't quit the application

I have this simple piece of code in C# with GTK#:
Main.cs:
using System;
using Gtk;
namespace Apu{
class MainClass{
public static void Main(string[] args){
Application.Init();
new ShowForm();
Application.Run();
}
}
}
ShowForm.cs
public partial class ShowForm: Gtk.Window{
public ShowForm(): base(Gtk.WindowType.Toplevel){
MessageDialog md = new MessageDialog(
this,
DialogFlags.DestroyWithParent,
MessageType.Error,
ButtonsType.None,
"Test"
);
md.SetPosition(Gtk.WindowPosition.CenterAlways);
md.Title = "Test window";
md.AddButton("Don't stop", ResponseType.Ok);
md.AddButton("Stop", ResponseType.Cancel);
ResponseType result = (ResponseType)md.Run();
if (result.Equals(ResponseType.Cancel)) {
Console.WriteLine("Quit!");
md.DestroyEvent += delegate {
Application.Quit();
};
/*md.DeleteEvent += delegate {
Application.Quit();
};*/
}
md.Destroy();
}
}
Console outputs Quit!, but the program doesn't quit. Neither DestroyEvent nor DeleteEvent works. Can anyone explain why? This is my first app in c#, my first time using gtk#. I use monodevelop as my IDE.
EDIT
Application.Exit() gives error: Gtk.Application does not contain a definition for 'Exit'.
If you wish to close the process, try Environment.Exit(0)
Try:
md.Destroyed += delegate {
Application.Quit();
};

Shared Variable between Watin ApartmentState.STA Thread and parent Thread?

I'm trying to get Watin working in my SSIS script Task to do some automation by opening IE in a new thread, do something, find the final value and basically return/set that value in the parent thread.
So I have this for now:
public partial class TestWatin{
public void Main()
{
String finalValueFromWeb = "";
Thread runnerThread = new Thread(delegate() { getDAFValue(ref finalValueFromWeb ); });
runnerThread.ApartmentState = ApartmentState.STA;
runnerThread.Start();
runnerThread.Join();
MessageBox.Show(finalValueFromWeb);
//here i want to use the value of finalValueFromWeb to download a file
//but if i try to access finalValueFromWeb the process would fail.
}
//do the Watin stuff here
public void findHiddenURL(String refObject)
{
//setup page controls, press search, grab the value of "hiddenURL"
IE ie = new IE("some_webadress_to_go_to");
ie.Visible = false;
ie.SelectList("testID1").Option("Car").Select();
ie.SelectList("testID2").Option("JAP").Select();
ie.SelectList("testID3").Option("2012").Select();
ie.Button("testSearch").Click();
Link link = ie.Link("hiddenURL");
ie.Close();
//fails here?
refObject = link.Url;
}
}
What I basically want to is for findHiddenURL() to find me a value which is a string containing some CSV url. I then want to use that string to download the CSV and process it.
The problem is when I try to set the value of finalValueFromWeb inside findHiddenURL() where the process fails. The Exception message says The Object Reference is not set to an instance of an object
Can someone please tell me how I should be going about this problem? What is the proper way of doing this type of thing? Thanks
Make the variable a member of the class and try to lock it. You can use c# lock :
http://msdn.microsoft.com/en-us/library/c5kehkcz%28v=vs.71%29.aspx
protected string finalValueFromWeb ;
....
public void Main()
{
...
lock(finalValueFromWeb)
{
MessageBox.Show(finalValueFromWeb);
}
}
public void findHiddenURL(String refObject)
{
...
lock(finalValueFromWeb)
{
finalValueFromWeb = link.Url;
}
}

IE Automation - Calling a client JavaScript **method** from server-side C#

Given the following C# code:
public object CallJavaScriptFunction(string functionName, params object[] args)
{
object script = Document.Script;
var result = script.GetType().InvokeMember(functionName, BindingFlags.InvokeMethod, null, script, args);
return result;
}
And the following client-side JavaScript block:
function someFunction() {
alert('This is only a test!');
}
var someObj = {
someMethod: function() {
alert('This is another test!');
}
}
The following server-side block executes successfully:
CallJavaScriptFunction("someFunction");
But this will throw a DISP_E_UNKNOWNNAME:
CallJavaScriptFunction("someOBj.someMethod");
Obviously I'm doing something wrong here - probably there is another way of calling InvokeMember on JavaScript instance methods, but I was not able to find out how.
Any thoughts? Any help will be appreciated.
You need to invoke the someObj property, then invoke the someMethod method on the value of the property

Categories