5

My current code works on elements outside of an iframe. How should I approach fetching elements within an iframe using getElementById? My end goal is to write text within the the <body id="tinymce"><p>...</p></body> tags. I am not using a webBrowser control - this is for an external instance of iexplore

HTML Sample

enter image description here

Code Sample

foreach (InternetExplorer ie in new ShellWindowsClass())
{
    if (ie.LocationURL.ToString().IndexOf("intranet_site_url") != -1)
    {
        IWebBrowserApp wb = (IWebBrowserApp)ie;
        while (wb.Busy) { Thread.Sleep(100); }
        HTMLDocument document = ((HTMLDocument)wb.Document);

        // FETCH BY ID
        IHTMLElement element;
        HTMLInputElementClass hitem;

        element = document.getElementById("tinymce");
        hitem = (HTMLInputElementClass)element;
        hitem.value = first_name;

        // FETCH BY ID in IFRAME
        IHTMLFramesCollection2 hframes = document.frames;
        for (int i = 0; i < hframes.length; i++)
        {
            object ref_index = i;
            IHTMLWindow2 currentFrame = (IHTMLWindow2)hframes.item(ref ref_index);

            if (currentFrame != null)
            {
                MessageBox.Show(currentFrame.name);
                // what to do from here?
            }
            else
                MessageBox.Show("Null");
        }
    }
}

- update idea Chance of adapting my idea below?

if (currentFrame != null)
{
    MessageBox.Show(currentFrame.name);

    HTMLDocument document_sub = ((HTMLDocument)currentFrame.document);
    IHTMLElement element_sub;
    HTMLInputElementClass hitem_sub;

    element_sub = (document_sub.getElementById("tinymce"));
    hitem_sub = (HTMLInputElementClass)element_sub;
    try
    {
        hitem_sub.value = first_name;

        // the above will produce...
        // InvalidCastException: Unable to cast COM object of type 'mshtml.HTMLBodyCLass' to class type 'mshtml.HTMLInputElementClass'
    }
    catch { }
}
Patrick Alexson
  • 153
  • 1
  • 4
  • 13
  • Is the subframe in a different origin than the parent document? If so, you can't simply reach in and grab its document object due to how the SameOriginPolicy restriction was implemented. You instead have to grab the subframe using its IOleContainer interface... – EricLaw Aug 27 '13 at 22:49
  • No, same origin - it is exactly how it is shown above in the html sample. I've never seen TinyMCE presented in markup this way and it doesn't make sense to me but I have to work with it. – Patrick Alexson Aug 27 '13 at 22:57
  • @PatrickAlexson, it might be easier to inject some JavaScript to get the DOM element you need, like [this](http://pastebin.com/iM5829MM) (originally from [here](http://stackoverflow.com/questions/18342200/how-do-i-call-eval-in-ie-from-c/)). You should be able to do the same in C# using `dynamic`. – noseratio Aug 30 '13 at 14:04
  • might be this answer is useful for you : http://stackoverflow.com/q/35651305/3555828 – Vishal Sen Feb 27 '16 at 10:20

4 Answers4

4

This answer was inspired by some research I recently did on using eval to inject script into an out-of-proc instance of Internet Explorer.

The idea is to bypass MSHTML DOM interop interfaces and use dynamic JavaScript to obtain a DOM object of interest. There are some implications:

To address the question itself, it should be quite easy to get the desired body element, using the approach illustrated below:

var tinymceBody = DispExInvoker.Invoke(ie.Document.parentWindow, "eval", "document.getElementById('decrpt_ifr').contentWindow.document.getElementById('tinymce')");

Here's a sample which executes alert(document.URL) in the context of a child frame of jsfiddle.net, by automating an out-of-proc instance of InternetExplorer.Application:

private async void Form1_Load(object sender, EventArgs ev)
{
    SHDocVw.InternetExplorer ie = new SHDocVw.InternetExplorer();
    ie.Visible = true;

    var documentCompleteTcs = new TaskCompletionSource<bool>();
    ie.DocumentComplete += delegate
    {
        if (documentCompleteTcs.Task.IsCompleted)
            return;
        documentCompleteTcs.SetResult(true);
    };

    ie.Navigate("http://jsfiddle.net/");
    await documentCompleteTcs.Task;

    // inject __execScript code into the top window
    var execScriptCode = "(window.__execScript = function(exp) { return eval(exp); }, window.self)";
    var window = DispExInvoker.Invoke(ie.Document.parentWindow, "eval", execScriptCode);

    // inject __execScript into a child iframe
    var iframe = DispExInvoker.Invoke(window, "__execScript", 
        String.Format("document.all.tags('iframe')[0].contentWindow.eval('{0}')",  execScriptCode));

    // invoke 'alert(document.URL)' in the context of the child frame
    DispExInvoker.Invoke(iframe, "__execScript", "alert(document.URL)");
}

/// <summary>
/// Managed wrapper for calling IDispatchEx::Invoke
/// https://stackoverflow.com/a/18349546/1768303
/// </summary>
public class DispExInvoker
{
    // check is the object supports IsDispatchEx
    public static bool IsDispatchEx(object target)
    {
        return target is IDispatchEx;
    }

    // invoke a method on the target IDispatchEx object
    public static object Invoke(object target, string method, params object[] args)
    {
        var dispEx = target as IDispatchEx;
        if (dispEx == null)
            throw new InvalidComObjectException();

        var dp = new System.Runtime.InteropServices.ComTypes.DISPPARAMS();
        try
        {
            // repack arguments
            if (args.Length > 0)
            {
                // should be using stackalloc for DISPPARAMS arguments, but don't want enforce "/unsafe"
                int size = SIZE_OF_VARIANT * args.Length;
                dp.rgvarg = Marshal.AllocCoTaskMem(size);
                ZeroMemory(dp.rgvarg, size); // zero'ing is equal to VariantInit
                dp.cArgs = args.Length;
                for (var i = 0; i < dp.cArgs; i++)
                    Marshal.GetNativeVariantForObject(args[i], dp.rgvarg + SIZE_OF_VARIANT * (args.Length - i - 1));
            }

            int dispid;
            dispEx.GetDispID(method, fdexNameCaseSensitive, out dispid);

            var ei = new System.Runtime.InteropServices.ComTypes.EXCEPINFO();
            var result = Type.Missing;
            dispEx.InvokeEx(dispid, 0, DISPATCH_METHOD, ref dp, ref result, ref ei, null);
            return result;
        }
        finally
        {
            if (dp.rgvarg != IntPtr.Zero)
            {
                for (var i = 0; i < dp.cArgs; i++)
                    VariantClear(dp.rgvarg + SIZE_OF_VARIANT * i);
                Marshal.FreeCoTaskMem(dp.rgvarg);
            }
        }
    }

    // interop declarations

    [DllImport("oleaut32.dll", PreserveSig = false)]
    static extern void VariantClear(IntPtr pvarg);
    [DllImport("Kernel32.dll", EntryPoint = "RtlZeroMemory", SetLastError = false)]
    static extern void ZeroMemory(IntPtr dest, int size);

    const uint fdexNameCaseSensitive = 0x00000001;
    const ushort DISPATCH_METHOD = 1;
    const int SIZE_OF_VARIANT = 16;

    // IDispatchEx interface

    [ComImport()]
    [Guid("A6EF9860-C720-11D0-9337-00A0C90DCAA9")]
    [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
    internal interface IDispatchEx
    {
        // IDispatch
        int GetTypeInfoCount();
        [return: MarshalAs(UnmanagedType.Interface)]
        System.Runtime.InteropServices.ComTypes.ITypeInfo GetTypeInfo([In, MarshalAs(UnmanagedType.U4)] int iTInfo, [In, MarshalAs(UnmanagedType.U4)] int lcid);
        void GetIDsOfNames([In] ref Guid riid, [In, MarshalAs(UnmanagedType.LPArray)] string[] rgszNames, [In, MarshalAs(UnmanagedType.U4)] int cNames, [In, MarshalAs(UnmanagedType.U4)] int lcid, [Out, MarshalAs(UnmanagedType.LPArray)] int[] rgDispId);
        void Invoke(int dispIdMember, ref Guid riid, uint lcid, ushort wFlags, ref System.Runtime.InteropServices.ComTypes.DISPPARAMS pDispParams, out object pVarResult, ref System.Runtime.InteropServices.ComTypes.EXCEPINFO pExcepInfo, IntPtr[] pArgErr);

        // IDispatchEx
        void GetDispID([MarshalAs(UnmanagedType.BStr)] string bstrName, uint grfdex, [Out] out int pid);
        void InvokeEx(int id, uint lcid, ushort wFlags,
            [In] ref System.Runtime.InteropServices.ComTypes.DISPPARAMS pdp,
            [In, Out] ref object pvarRes,
            [In, Out] ref System.Runtime.InteropServices.ComTypes.EXCEPINFO pei,
            System.IServiceProvider pspCaller);
        void DeleteMemberByName([MarshalAs(UnmanagedType.BStr)] string bstrName, uint grfdex);
        void DeleteMemberByDispID(int id);
        void GetMemberProperties(int id, uint grfdexFetch, [Out] out uint pgrfdex);
        void GetMemberName(int id, [Out, MarshalAs(UnmanagedType.BStr)] out string pbstrName);
        [PreserveSig]
        [return: MarshalAs(UnmanagedType.I4)]
        int GetNextDispID(uint grfdex, int id, [In, Out] ref int pid);
        void GetNameSpaceParent([Out, MarshalAs(UnmanagedType.IUnknown)] out object ppunk);
    }
}
Community
  • 1
  • 1
noseratio
  • 59,932
  • 34
  • 208
  • 486
  • 1
    http://stackoverflow.com/questions/27566985/browser-execscript-stopped-working-after-updating-windows You made a valiant effort to provide a means to access "eval", unfortunately however microsoft killed off virtually any out-of-process means to run *any* function (not just "eval" or "execScript") under the window (IHTMLWindow[X]) object. I will be more than happy if someone runs some tests and proves me wrong on this claim though (who knows microsoft might roll a patch to somehow fix this situation ...). – XDS Dec 22 '14 at 19:27
2

Try this:

Windows.Forms.HtmlWindow frame = WebBrowser1.Document.GetElementById("decrpt_ifr").Document.Window.Frames["decrpt_ifr"];
HtmlElement body = frame.Document.GetElementById("tinymce");
body.InnerHtml = "Hello, World!";

That gets the frame and treats it as a different document (because it is) and then it tries to get the element from its id. Good luck.

Edit: This should do the trick taking advantage of the dynamic datatype, and InternetExplorer interface:

private void Form1_Load(object sender, EventArgs e)
{
    foreach (InternetExplorer ie in new ShellWindows())
    {
        if (ie.LocationURL.ToString().IndexOf("tinymce") != -1)
        {
            IWebBrowserApp wb = (IWebBrowserApp)ie;
            wb.Document.Frames.Item[0].document.body.InnerHtml = "<p>Hello, World at </p> " + DateTime.Now.ToString();
        }
    }
}
Hanlet Escaño
  • 17,114
  • 8
  • 52
  • 75
  • Thanks for the assistance @hanlet, this would appear to be only for a webBrowser control, yes? I say that because in my case im talking to an external instance of iexplore.exe using `foreach (InternetExplorer ie in new ShellWindowsClass()) { if (ie.LocationURL.ToString().IndexOf("intranet_site_url") != -1) { // code in original post starts here } }` So if I understand correctly, I need to treat markup in an iframe as a new document. Could I use something like in the update I just made on the original post? – Patrick Alexson Aug 27 '13 at 22:44
  • @PatrickAlexson sorry, I was gone. I will take a look at it now. – Hanlet Escaño Aug 28 '13 at 02:01
  • @PatrickAlexson check my edit out. This worked on the TinyMCE website, I pressume it should work on your local site. I will favorite this question because this is something that could come in handy in the future ;) – Hanlet Escaño Aug 28 '13 at 02:53
  • Thanks for the assistance! Frames is unavailable, see: [http://imgur.com/UkiAhJP](http://imgur.com/UkiAhJP) – Patrick Alexson Aug 28 '13 at 15:20
  • @PatrickAlexson the example wont probably fixed your problem because they are different pages. Unless you post your page somewhere online that I can take a look, the scenario is going to be different. – Hanlet Escaño Aug 28 '13 at 15:22
  • Is there a public repository that I can throw a copy of this code up to, idk, something like a fiddle? – Patrick Alexson Aug 28 '13 at 15:28
  • let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/36403/discussion-between-hanlet-escano-and-patrick-alexson) – Hanlet Escaño Aug 28 '13 at 15:29
  • 1
    `dynamic` doesn't work for Internet Explorer interfaces marshaled out-of-proc. This can be done using [IDispatchEx](http://stackoverflow.com/a/18546866/1768303) though. – noseratio Sep 02 '13 at 09:01
1

Explore the sandbox attribute.

http://www.w3schools.com/tags/att_iframe_sandbox.asp

Someone
  • 121
  • 1
  • 7
0

Also another way would be to get url of that iframe and load that into your browser.

For me accepted solution is giving "Access is denied. (Exception from HRESULT: 0x80070005 (E_ACCESSDENIED))" error.

marcanuy
  • 23,118
  • 9
  • 64
  • 113