Skybound Software is looking to hire experienced C# developers to work on Stylizer and other forthcoming Skybound products. Click here for details.
GeckoFX is an open-source component which makes it easy to embed Mozilla Gecko (Firefox) into any .NET Windows Forms application. Written in clean, fully commented C#, GeckoFX is the perfect replacement for the default Internet Explorer-based WebBrowser control. Download GeckoFX @ Google Code >
GeckoFX is made by Skybound Software, authors of Stylizer—the professional CSS editor with a strong focus on visual control, error-free workflow, and productivity. Learn About Stylizer >
Discuss GeckoFX, the Open-Source Gecko Control for .NET
You are not logged in.
Pages: 1
I am hosting a GeckoWebBrowser object inside a Windows Form. I have no problem instantiating the browser and navigating to a given page, but I need more functionality than that.
Does anyone know how to capture/intercept/hook (etc) any given JS event in the currently loaded document?
for example...I have loaded a document into my GeckoWebBrowser and it has the following:
script type="text/javascript">
function mySimpleFunction(myParam)
{
alert(myParam);
}
</script>I want to know whenever that function is called. Any time the function runs, I want it to trigger an event in my host application (the Windows Form).
Any ideas how to do this?
Thx in advance!!!
R,
Jeff
Using GeckoFX 1.9.1.0
Using XULRunner 1.9.1.7
Last edited by JeffJohnson (January 15, 2010 2:40 PM)
Offline
Hi!
This is quite a complex thing to do actually.
I 'll post some info as soon as I've structured it in a comprehensible manner.
But basically it's a hack comprising of the following things:
Having a routingeventhandler
Adding/Removing a "javascript" lisner
Raising your own javascript events and routing them to C#
code examples to come soon...
Offline
Hi KrippZ
Any update regarding your sample code, I am interested in same thing. I want to invoke a native function (C#) from javascript. I thought maybe this could be done by using XPCOM object which both javascript and C# can create (ie. Xpcom.cs) and use to route messages to each other. I am not sure it will work since i am very new to all this. Is this similar to your idea?
FYI: If I get some thing working i will post it here.
Regards
Offline
So I've constructed this hack from various threds on the fourm.
credits need to be given to :
softwerx, M1Labbe, hex1848
The basic idea for this to work is:
JS -> C#
Raise custom DOM events in javascript
Listen to the custom dom events in C# - through the use of "DOMContentLoaded" event
C# -> JS - a bit trickier
Append Javascript to DOM onLoad.
Background reading:
http://geckofx.org/viewtopic.php?pid=1735#p1735
http://geckofx.org/viewtopic.php?id=737
http://geckofx.org/viewtopic.php?id=453
I recommend that you read the theads above. First things first, you have to implement the "DOMContentLoaded" event.
follow th steps in http://geckofx.org/viewtopic.php?id=737
Now you are going to need the RouteEventHandler.cs by [M1Labbe], I 've done some modifications to it.
you can find the original here http://geckofx.org/viewtopic.php?pid=1735#p1735
Create a file called RouteEventHandler.cs
/// <summary>
/// There event args will contain the javascript event that was raised along with its JSONString.
/// </summary>
public class DOMJSONStringEventArgs : System.EventArgs
{
private string strJSEventName = "";
public string JSEventName
{
get { return strJSEventName; }
}
private string strJSONString = "";
public string JSONString
{
get { return strJSONString; }
}
public DOMJSONStringEventArgs(nsAString JSEventName, nsAString strJSONString)
{
this.strJSEventName = JSEventName.ToString();
this.strJSONString = strJSONString.ToString();
}
}
/// <summary>
/// This class is a wrapper to the underlying IDOMEventListner.
/// It re-raises a C# event with apropriate event args.
/// </summary>
public class RouteEventHandler: nsIDOMEventListener
{
private EventHandler<DOMJSONStringEventArgs> handler;
public const string JS_ELEMENT_NAME = "JSONString";
public RouteEventHandler(EventHandler<DOMJSONStringEventArgs> oHandler)
{
this.handler = oHandler;
}
#region nsIDOMEventListener Members
void nsIDOMEventListener.HandleEvent(nsIDOMEvent e)
{
nsAString jsEventName = new nsAString();
nsAString attribute = new nsAString(JS_ELEMENT_NAME);
nsAString value = new nsAString();
nsIDOMElement target = e.GetTarget() as nsIDOMElement;
target.GetAttribute(attribute, value);
e.GetType(jsEventName);
DOMJSONStringEventArgs eArgs = new DOMJSONStringEventArgs(jsEventName, value);
if (this.handler != null)
this.handler(target, eArgs);
jsEventName.Dispose();
attribute.Dispose();
value.Dispose();
}
#endregion
}Now create a new file SimpleObjectForScripting.cs
public class SimpleObjectForScripting
{
private nsIDOMWindow oDomWindow;
private static string CLIENT_SIDE_JAVASCRIPT_ID = "invokescript";
private Dictionary<int, RouteEventHandler> oEventListeners = new Dictionary<int, RouteEventHandler>();
private Dictionary<int, nsAString> oEventTypes = new Dictionary<int, nsAString>();
internal SimpleObjectForScripting(ref nsIDOMWindow DomWindow)
{
oDomWindow = DomWindow;
}
#region EventHandlers - Recive event FROM javascript
/// <summary>
/// Add a C# listner to the xpcom listner. This is done by wrappinig the xpcom event.
/// </summary>
/// <param name="Type">The name of the eventtype, for e.g 'MyCustomEvent'. This must be the same as the EventName in the JavaScript.</param>
/// <param name="Handler">The handler for this event</param>
public void AddEventListener(string Type, EventHandler<DOMJSONStringEventArgs> Handler)
{
RouteEventHandler Listener = new RouteEventHandler(Handler);
if (oEventListeners.ContainsKey(Handler.Method.GetHashCode()) == true && oEventTypes.ContainsKey(Handler.Method.GetHashCode()))
{
oEventListeners.Remove(Handler.Method.GetHashCode());
oEventTypes.Remove(Handler.Method.GetHashCode());
}
oEventListeners.Add(Handler.Method.GetHashCode(), Listener);
nsAString nsType = new nsAString(Type);
oEventTypes.Add(Handler.Method.GetHashCode(), nsType);
(oDomWindow as nsIDOMEventTarget).AddEventListener(nsType, Listener, false);
}
/// <summary>
/// Remove C# listner from the xpcom listner.
/// </summary>
/// <param name="Type">The name of the eventtype, for e.g 'MyCustomEvent'. This must be the same as the EventName in the JavaScript.</param>
/// <param name="Handler">The handler for this event</param>
public void RemoveEventListener(string Type, EventHandler<DOMJSONStringEventArgs> Handler)
{
nsIDOMEventTarget target = oDomWindow as nsIDOMEventTarget;
nsAString strType = oEventTypes[Handler.Method.GetHashCode()] as nsAString;
RouteEventHandler route = oEventListeners[Handler.Method.GetHashCode()] as RouteEventHandler;
target.RemoveEventListener(strType, route, false);
oEventListeners.Remove(Handler.Method.GetHashCode());
oEventTypes.Remove(Handler.Method.GetHashCode());
}
#endregion
#region JavaScript helper Methods - Execute Javascript
/// <summary>
/// This will run arbitraty javascript on the client. It creates a script block in the DOM tree and the javascript gets executed.
/// The script block is not permanent. It will be removed on the next call to this method, and replaced by new arbitarty code.
/// </summary>
/// <param name="strSourceJavaScript">The JavaScript Code</param>
/// <param name="wrapTryCatch">If true it will att a javascript try/catch block for the incomming code.</param>
public void RunRawJavaScriptOnClient(string strSourceJavaScript, bool wrapTryCatch)
{
if (strSourceJavaScript.Length > 0)
{
if (wrapTryCatch == false)
{
ScriptBuilder(strSourceJavaScript);
}
else
{
ScriptBuilder("try {" + strSourceJavaScript + "}catch(anError){ dump(anError); alert(anError); }");
}
}
}
/// <summary>
/// Appends a script block with the id of CLIENT_SIDE_JAVASCRIPT_ID, adds it to the dom tree
/// </summary>
/// <param name="strSourceJavaScript">Javascript code.</param>
private void ScriptBuilder(string strSourceJavaScript)
{
GeckoDocument doc = GeckoDocument.Create((nsIDOMHTMLDocument)oDomWindow.GetDocument());
GeckoElement ge = doc.GetElementById(CLIENT_SIDE_JAVASCRIPT_ID);
if (ge != null)
doc.Body.RemoveChild(ge);
ge = doc.CreateElement("script");
ge.SetAttribute("type", "text/javascript");
ge.SetAttribute("id", CLIENT_SIDE_JAVASCRIPT_ID);
StringBuilder textContent = new StringBuilder();
textContent.Append(strSourceJavaScript);
ge.TextContent = textContent.ToString();
doc.Body.AppendChild(ge);
}
#endregion
}Now create a javascript file myEvents.js - for example
add this
function ApplicationCommunication()
{
this.ElementName = "ToAppData"; //This is the virtual element used in the event dispatching process.
this.AttributeName = "JSONString"; //the atribut to read values from on the application side of the event.
this.raiseEvent = function(evtName, strJSONData)
{
var element = document.createElement(this.ElementName);
element.setAttribute("JSONString", strJSONData);
document.documentElement.appendChild(element);
var evt = document.createEvent("Events");
evt.initEvent(evtName, true, false);
element.dispatchEvent(evt);
document.documentElement.removeChild(element);
}
}Add the script to your test webpage.
<script type="text/Javascript" src="myEvent.js"></script>
<script>
//use for the raising of custom events
var et = new ApplicationCommunication();
//simple test function in JS
function Test()
{
et.raiseEvent('MyExtensionEvent','{"name":"krippz", "address":"Göteborg"}'); //raise the event with custom JSON formatted string. note that 'MyExtensionEvent' will be used later in C#
}
</script>Add a button to test the function
<input type="button" value="TestBtn" onClick="Test();"/>
Now open the file GeckoDom.cs
find public class GeckoWindow
add
private SimpleObjectForScripting oScripter;
public SimpleObjectForScripting Scripter
{
get{ return oScripter;}
}find private GeckoWindow(nsIDOMWindow window)
add after _DomWindow = window;
oScripter = new SimpleObjectForScripting(ref window);
---------------- JS -> C# --------------------------
So now we have to setup the listners and handlers, in your form you should be able to
Setup a listner for DomContentLoaded, this can be done by using the FormsDesigner.
Inside the geckoWebBrowser1_DOMContentLoaded(object sender, GeckoDomEventArgs e) add the following:
Setup of listner
this.geckoWebBrowser1.Window.Scripter.AddEventListener("MyExtensionEvent", new EventHandler<DOMJSONStringEventArgs>(OnMyExtensionEvent));Setup of handler
void OnMyExtensionEvent(object sender, DOMJSONStringEventArgs e)
{
//dosomething
}--------------- C# -> JS ---------------------------
Just use
this.geckoWebBrowser1.Window.Scripter.RunRawJavaScriptOnClient(javascript_string);
This solution is not pretty but is works and fills my needs, if someone has improvments please drop a line :)
O and if you have any questions, just post them and I'll try to anwser a.s.a.p
/Cheers
KrippZ
Offline
@ KrippZ
Awesome information...thank you so much for posting this.
I am having some problems compiling, however. I cannot seem to find the "nsIDOMEventListener" object anywhere. Any ideas why I'm not seeing this? Am I missing a reference somewhere?
R,
Jeff
Last edited by JeffJohnson (January 19, 2010 1:04 PM)
Offline
Hi Jeff!
Look in the nsInterfaces.cs file
you should find this:
[Guid("df31c120-ded6-11d1-bd85-00805f8ae3f4"), ComImport,InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
interface nsIDOMEventListener
{
void HandleEvent(nsIDOMEvent e);
}The RouteEventHandler implements the nsIDOMEventListner interface.
/Cheers
Krippz
Offline
Web Slap me for not doing my reasearch...should have read these before posting blindly
KrippZ wrote:
Background reading:
http://geckofx.org/viewtopic.php?pid=1735#p1735
http://geckofx.org/viewtopic.php?id=737
http://geckofx.org/viewtopic.php?id=453
I was trying to implement the code using a reference to the compiled DLL instead of going into the actual source code and modifying it...
I'm working on the implementation you provided now...I'll let you know how it goes.
Thanks again!!!!!!!
Last edited by JeffJohnson (January 19, 2010 3:19 PM)
Offline
@KrippZ
Fantastic. It works flawlessly. I had the following problem...
couldn't find the "DomContentLoaded" event and therefore I couldn't add a handler for it...however...I was able to use the "Navigated" event as a juncture to identify when the DOM was completely loaded, and simply called the Scripter.AddEventListener once the document was fully loaded.
This works as I need for my current situation. While it does not give me the ability to monitor every Javascript call happening on the DOM, it does give me the opportunity to structure my Javascript to raise events that my hosting application can handle.
I appreciate your work. Big kudos to you!!!
Last edited by JeffJohnson (January 20, 2010 10:24 AM)
Offline
Thanks KrippZ it works great.
I was going about it the wrong way, i created a XPCOM object which can be consumed by both native and javascript and then used to pass data back and forth. Saved me ton of time. Thanks a bunch.
Offline
Hi Jeff
I saw your post
couldn't find the "DomContentLoaded" event and therefore I couldn't add a handler for it...however...I was able to use the "Navigated" event as a juncture to identify when the DOM was completely loaded, and simply called the Scripter.AddEventListener once the document was fully loaded.
Have you read the thread
http://geckofx.org/viewtopic.php?id=737
In that post I describe how I got the DOMContentLoaded, it gets fired by default by xulrunner.
I use xulrunner 1.9.1.4 i think.
The Navigated is good but I've read somewhere that the DOMContentLoaded get fired prior to navigated. And that if you use navigated and a page loads slowly this can impact on any JS you'd like to run.
Anyway I'm glad I be of service :)
Offline
@KrippZ
Awesome...I'll read up on your post about DOMLoaded...but thanks for the tip. I have been busy, in the meantime, working on some jQuery and ASP.NET AJAX integration...but I am back on GeckoFX and I'll recompile my code with the DOMContentLoaded code you posted.
Great resources...thank you so much!
Offline
for my current application projects I originally was using the Trident WebBrowser Wrapper control provided by the ,net framework. But really, whom in their right mind would want to release an app with that browser embedded in it????? I mean what a piece of garbage. So i sought out to find a solution to use either Firefox(Gecko) or Chrome(Webkit) in my application instead. And that lead me here a while ago. i downloaded the wrapper , and originally had some troubles getting it going, and at the time life for me was complete chaos so i switch back to trident, and continued onward.
Now life has slowed down a bit for me and now i am really wanting to use Gecko or Webkit. Chrome is my main browser so WebKit is my first choice, and while i can easily implement it as a standard browser (IE: visit this site or that site) the current .net wrappers do not have anything close to html editing features implemented yet. SO back to Gecko. And while i can easily get my html doc into designmode with this wrapper getting the execCommands to work without throwing a com exception is a problem. No matter what i do it fails on simple commands like bolding text or whatever. So now i am left implementing Gecko as a editor with the editor being javascript, but again, this wrapper has no native 'invokescript' method. I became sad at this finding realizing that i might just be stuck with trident :(
Then i found this thread, and it was like xmas just happened and santa brought me everything i ever asked for :P
Awesome resource, compiled without a hitch, but i have not yet tested it i am still working on the javascript code for html editing so once that is done i will let you know that goes.
Cheers and thanks again for a great resource.
Offline
Hello,
listening to the JavaScript Events works fine, thanks, but i got an error, when i want to run a javascript in the page (this.geckoWebBrowser1.Window.Scripter.RunRawJavaScriptOnClient("HelloWorld('hello')",false);)
-------------------
Unable to cast COM object of type 'System.__ComObject' to interface type 'Skybound.Gecko.nsIDOMNSElement'. This operation failed because the QueryInterface call on the COM component for the interface with IID '{CEA6F919-7FE6-4BDD-9DB6-158D9283F8D3}' failed due to the following error: No such interface supported (Exception from HRESULT: 0x80004002 (E_NOINTERFACE)).
----------------------
What's wrong there?
Offline
Hey guys, is there any sample code on how to do this using VB .net .
I could really use this for a web application that I am making.
I want to pass VB code when they click on a link.
Last edited by RDx (April 17, 2010 6:58 PM)
Offline
[Crazy! it works without any change~ ignore below questions]
Hi KrippZ,
thank you for the code, this is exactly what i need for 2-way communication between javascript and c#.
however, i have a problem to make C# capture javascript event. i followed all your steps and currently, the DOMContentLoaded event was added, and in the handler i register my C# handler for javascript event.
but during debug, there is no chance to run into
void nsIDOMEventListener.HandleEvent(nsIDOMEvent e) in RouteEventHandler (the handler has been registered in SimpleObjectForScripting followed your code)
and i also debug void nsIDOMEventListener.HandleEvent(nsIDOMEvent e) in GeckoWebBrowser, only MouseMove, Click and such native event are captured.
so anything i missed?
1, i tried both xulrunner-1.9.2.en-US.win32 and 1.9
2, i can add a handler in JS directly, and the event can be handled.
3, directly add handler in GeckoWebBrowser in OnHandleCreated makes no help:
target.AddEventListener(new nsAString("MyExtensionEvent"), this, true);
4, if i change the addListener directly in GeckoElement, and use an exist element in JS to raise event. register C# handler in the exist GeckoElement by getElementByID, then my C# handler can be invoked.
so the bubbles have problem? i checked it is true and the JS handler can be invoked successfully. the bubbles have problem in C#?
Last edited by efinal (June 24, 2010 3:57 AM)
Offline
Pages: 1