【问题标题】:getElementById on element within an iframegetElementById 在 iframe 中的元素上
【发布时间】:2013-08-30 20:02:36
【问题描述】:

我当前的代码适用于 iframe 之外的元素。我应该如何使用 getElementById 在 iframe 中获取元素?我的最终目标是在 <body id="tinymce"><p>...</p></body> 标签内写文本。我没有使用 webBrowser 控件 - 这是用于 iexplore 的外部实例

HTML 示例

代码示例

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");
        }
    }
}

- 更新想法 有机会在下面调整我的想法吗?

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 { }
}

【问题讨论】:

  • 子框架与父文档的来源不同吗?如果是这样,由于 SameOriginPolicy 限制的实现方式,您不能简单地进入并获取其文档对象。相反,您必须使用其 IOleContainer 接口来获取子帧...
  • 不,相同的来源 - 这正是上面 html 示例中显示的方式。我从未见过 TinyMCE 以这种方式呈现在标记中,这对我来说没有意义,但我必须使用它。
  • @PatrickAlexson,注入一些 JavaScript 来获取所需的 DOM 元素可能更容易,例如 this(最初来自 here)。您应该可以在 C# 中使用 dynamic 执行相同的操作。
  • 这个答案可能对你有用:stackoverflow.com/q/35651305/3555828

标签: c# internet-explorer mshtml


【解决方案1】:

另一种方法是获取该 iframe 的 url 并将其加载到浏览器中。

对我来说,接受的解决方案是给出"Access is denied. (Exception from HRESULT: 0x80070005 (E_ACCESSDENIED))" 错误。

【讨论】:

    【解决方案2】:

    这个答案的灵感来自 some research 我最近使用 eval 将脚本注入 Internet Explorer 的进程外实例。

    这个想法是绕过 MSHTML DOM 互操作接口并使用动态 JavaScript 来获取感兴趣的 DOM 对象。有一些含义:

    要解决问题本身,使用下面说明的方法应该很容易获得所需的body 元素

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

    这是一个在jsfiddle.net 子框架的上下文中执行alert(document.URL) 的示例,通过自动化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);
        }
    }
    

    【讨论】:

    • stackoverflow.com/questions/27566985/… 您做出了英勇的努力来提供一种访问“eval”的方法,但不幸的是,微软几乎取消了所有运行 any 函数的进程外方法(不仅仅是“eval”或“execScript”)在窗口 (IHTMLWindow[X]) 对象下。如果有人运行一些测试并证明我在这个声明上是错误的,我会非常高兴(谁知道微软可能会推出补丁以某种方式解决这种情况......)。
    【解决方案3】:

    试试这个:

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

    获取框架并将其视为不同的文档(因为它是),然后尝试从其 id 获取元素。祝你好运。

    编辑:这应该可以利用dynamic 数据类型和InternetExplorer 接口:

    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 的帮助,这似乎仅适用于 webBrowser 控件,是吗?我这么说是因为在我的情况下,我使用foreach (InternetExplorer ie in new ShellWindowsClass()) { if (ie.LocationURL.ToString().IndexOf("intranet_site_url") != -1) { // code in original post starts here } } 与 iexplore.exe 的外部实例交谈,所以如果我理解正确,我需要将 iframe 中的标记视为新文档。我可以使用我刚刚在原始帖子中所做的更新中的内容吗?
    • @PatrickAlexson 对不起,我走了。我现在去看看。
    • @PatrickAlexson 检查我的编辑。这适用于 TinyMCE 网站,我认为它应该适用于您的本地网站。我会喜欢这个问题,因为这在未来可能会派上用场;)
    • 感谢您的帮助!框架不可用,请参阅:http://imgur.com/UkiAhJP
    • dynamic 不适用于进程外编组的 Internet Explorer 接口。这可以使用IDispatchEx 来完成。
    【解决方案4】:

    【讨论】:

    • 该属性不存在,我们对 HTML 标记具有只读访问权限。
    • @Spender,我非常清楚 w3schools 的无效性,但大多数情况下它是相当准确的。
    猜你喜欢
    • 2020-03-21
    • 2017-07-24
    • 2014-12-24
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多