How to Invoke Scripts Work in Mshtml

MSHTML : Calling member of javascript object?

FINALLY !! I got it up and running !

The reason for the

System.InvalidCastException

that was thrown, whenever I tried to reference the parentWindow of an mshtml.IHTMLDocument2 and / or assign it to an mshtml.IHTMLWindow2 window object had to do with Threading.

For some, unknown to me, reason it seems that the COM objects of mshtml.IHTMLWindow are operating on another Thread that must be of Single-Threaded Apartment (STA) state.

So the trick was, calling / executing the required piece of code on another Thread with STA state.

Here's a sample code:

SHDocVw.InternetExplorer IE = new SHDocVw.InternetExplorer

bool _isRunning = false;

private void IE_DocumentComplete(object pDisp, ref obj URL)
{
//Prevent multiple Thread creations because the DocumentComplete event fires for each frame in an HTML-Document
if (_isRunning) { return; }

_isRunning = true;

Thread t = new Thread(new ThreadStart(Do))
t.SetApartmentState(ApartmentState.STA);
t.Start();
}

private void Do()
{
mshtml.IHTMLDocument3 doc = this.IE.Document;

mshtml.IHTMLElement player = doc.getElementById("player");

if (player != null)
{
//Now we're able to call the objects properties, function (members)
object value = player.GetType().InvokeMember("getLastSongPlayed", System.Reflection.BindingFlags.InvokeMethod, null, player, null);

//Do something with the returned value in the "value" object above.
}
}

We're now also able to reference the parentWindow of an mshtml.IHTMLDocument2 object and execute a sites script and / or our own (remember it must be on an STA thread):

mshtml.IHTMLWindow2 window = doc.parentWindow;

window.execScript("AScriptFunctionOrOurOwnScriptCode();", "javascript");

This might save someone from headaches in the future. lol

Calling ActionScript (Flash) method via mshtml from C#

Ok, I've found an issue. Before this execScript. I was doing some modification of DOM and moving elements and apparently this has broken DOM so I couldn't call swfObject because during moving it was somehow modified and lost its properties and methods which were exported via ActionScript.

How to inject Javascript in WebBrowser control?

For some reason Richard's solution didn't work on my end (insertAdjacentText failed with an exception). This however seems to work:

HtmlElement head = webBrowser1.Document.GetElementsByTagName("head")[0];
HtmlElement scriptEl = webBrowser1.Document.CreateElement("script");
IHTMLScriptElement element = (IHTMLScriptElement)scriptEl.DomElement;
element.text = "function sayHello() { alert('hello') }";
head.AppendChild(scriptEl);
webBrowser1.Document.InvokeScript("sayHello");

This answer explains how to get the IHTMLScriptElement interface into your project.

Rundll32.exe javascript

There's a great explanation of this here: http://thisissecurity.net/2014/08/20/poweliks-command-line-confusion/

To summarize using the same example of:

rundll32.exe javascript:"\..\mshtml,RunHTMLApplication ";alert('foo');
  1. RunDll32
    1. Parses the command and decides the intended DLL is: javascript:"\..\mshtml
    2. Fails at loading that as an absolute path.
    3. Fails to find a match in the working directory or on the path.
    4. Fails to find a manifest javascript:"\..\mshtml.manifestfor the module.
    5. Calls LoadLibrary
  2. LoadLibrary
    1. Adds the extension and attempts to load javascript:"\..\mshtml.dll
    2. Treats this as relative, so it goes up from the fake javascript:"\ directory.
    3. Searches for mshtml.dll which it finds in the System directory.
    4. Loads the DLL using RunHTMLApplication as the entry point.
  3. RunHTMLApplication
    1. Attempts to execute the command ";alert('foo');
    2. As that's invalid Javascript it calls GetCommandLine for the original command which returns javascript:"\..\mshtml,RunHTMLApplication ";alert('foo');
    3. Attempts to open this URI so it asks the system how to handle the javascript protocol which is typically set to Microsoft HTML Javascript Pluggable Protocol in the registry.
    4. Then executes the Javascript: "..\mshtml,RunHTMLApplication ";alert('foo');
  4. Javascript
    1. The first statement creates a string and does nothing with it which is valid enough to not cause an error.
    2. Continues executing the rest of the script.

Do I absolutely need to call ReleaseComObject on every MSHTML object?

The RCW will release the COM object when the RCW is finalized, so you don't need to create a wrapper that does this. You call ReleaseComObject because you don't want to wait around for the finalization; this is the same rationale for the Dispose pattern. So creating wrappers that can be Disposed isn't a bad idea (and there are examples out there

For var doc = myBrowser.Document.DomDocument ...;, you should also capture .Document in a separate variable and ReleaseComObject it as well. Any time you reference a property of a COM object which produces another object, make sure to release it.

In GetAttribute, you're casting the element to another interface. In COM programming, that adds another reference. You'll need to do something like var htmlElement = (IHTMLElement) element; so you can release that as well.

Edit - this is the pattern to use when working with COM objects:

IHTMLElement element = null;
try
{
element = <some method or property returning a COM object>;
// do something with element
}
catch (Exception ex) // although the exception type should be as specific as possible
{
// log, whatever

throw; // not "throw ex;" - that makes the call stack think the exception originated right here
}
finally
{
if (element != null)
{
Marshal.ReleaseComObject(element);
element = null;
}
}

This should really be done for every COM object reference you have.

Running a JavaScript function in an instance of Internet Explorer

The problem had to do with threading - I've wasted so much time with STA issues you'd think I'd learn by now :).

Anyhow I found a way to get the second bit of code I posted working and running javascript functions in the IE window! Here is the code:

this.Dispatcher.Invoke(DispatcherPriority.Normal, (Action)(() =>
{

mshtml.HTMLDocument doc = ie.Document;

mshtml.IHTMLWindow2 win = doc.parentWindow as IHTMLWindow2;
win.execScript("init();", "javascript");

}));

Hope it helps someone!



Related Topics



Leave a reply



Submit