【问题标题】:Using CEFSharp ILifeSpanHandler interface to Handle Popups使用 CEFSharp ILifeSpanHandler 接口处理弹出窗口
【发布时间】:2018-12-14 18:15:39
【问题描述】:

我在处理弹出窗口时遇到问题。我已经从 IRequestHandler 实现了 ILifeSpanHandler 和 OnBeforeBrowse(以及其他)。

我如何知道在 ILifeSpanHandler 中调用了什么 URL?我无法在 OnAfterCreated 或 OnBeforePopup 中获得它。目前我首先在 OnBeforeBrowse 中看到它。

我没有代码,因为我的问题是“如何”。在 OnBeforePopup 中,我检查了 targetUrl,但它似乎是用来装饰的,因为我已经读到它无论如何都没有实现。我还查看了 browner/chromiumWebBrowser 对象,browser 和 newBroswer 似乎什么都不是。人们会期望 OnAfterCreated chromiumWebBrowser 会返回一个对象,但在我的情况下它什么都不是。

我正在测试以下内容

        Public Sub OnAfterCreated(chromiumWebBrowser As IWebBrowser, browser As IBrowser) Implements ILifeSpanHandler.OnAfterCreated

            Try
                Debug.Print(vbNewLine)
                Debug.Print("OnAfterCreated")
                Debug.Print(String.Concat("OnAfterCreated - MainFrame.Url "), browser.MainFrame.Url)
                Debug.Print("OnAfterCreated")
                Debug.Print(vbNewLine)

            Catch ex As Exception

            End Try
        End Sub

我有以下内容

 Public Function OnBeforePopup(chromiumWebBrowser As IWebBrowser, browser As IBrowser, frame As IFrame, targetUrl As String, targetFrameName As String, targetDisposition As WindowOpenDisposition, userGesture As Boolean, popupFeatures As IPopupFeatures, windowInfo As IWindowInfo, browserSettings As IBrowserSettings, ByRef noJavascriptAccess As Boolean, ByRef newBrowser As IWebBrowser) As Boolean Implements ILifeSpanHandler.OnBeforePopup

            Try
                Debug.Print(vbNewLine)
                Debug.Print("OnBeforePopup")
                Debug.Print(String.Concat("OnBeforePopup - targetUrl "), targetUrl)
                Debug.Print(String.Concat("OnBeforePopup - browser.MainFrame.Url "), browser.MainFrame.Url)
                Debug.Print(String.Concat("OnBeforePopup - chromiumWebBrowser.Address "), chromiumWebBrowser.Address)
                Debug.Print("OnBeforePopup")
                Debug.Print(vbNewLine)
            Catch ex As Exception

            End Try

            Return False
        End Function

【问题讨论】:

  • 请一次问一个问题。
  • 另外,请更新您的问题以显示您到目前为止所做的尝试。
  • 好吧,让我们提出 2 个问题。他们确实同时处理弹出窗口,但现在我无法识别它们,所以还没有必要知道如何关闭它们。
  • 你在哪里读到targetUrl没有实现?如果 window 由 javascript window.open 打开,targetUrl 可能为空
  • 我找不到它,但我想这就是我读到的。 magpcss.org/ceforum/viewtopic.php?f=6&t=196code.google.com/archive/p/chromiumembedded/issues/5 但是它已经过时了。

标签: cefsharp chromium-embedded


【解决方案1】:

我已经看到使用 ILifeSpanHandler 接口处理弹出窗口的不同方法。我在 Stack Overflow 中也看到了一种方法,该方法被接受为该特定问题的正确答案,即在 ILifeSpanHandler 的 OnBeforePopup 实现中返回 true,然后将 targetURL 参数传递给创建弹出窗口的处理程序。

这种方法非常不理想,因为您正在破坏实际打开弹出窗口的页面与弹出窗口本身之间的链接。如果您通过 JavaScript 访问弹出窗口中 window 对象的 opener 属性,您会注意到它为空。并且打开弹出窗口的页面永远不会知道弹出窗口实际上是打开的,因为返回 true 会取消创建。

另一种方法是让 Cef 创建弹出窗口,程序员只需决定是将浏览器显示为弹出窗口还是要控制的子窗口(如选项卡式浏览)。这是没有错误的,几乎是理想的。但是这种方法的问题是你无法监听 FrameLoadStart、FrameLoadEnd、AddressChanged、TitleChanged 等事件。

Cef 开发人员标记为实验性的一种方法是通过 newWebBrowser out 参数返回一个新的 IWebBrowser 实例。这有很多副作用。打开弹出窗口的页面当然会将弹出窗口识别为他的,尽管它不是它创建的原始浏览器 (IBrowser)。该页面可能会像 btcclicks.com 一样忽略它,在这种情况下,没有问题。但是像drops.xyz 这样的网站对他的东西非常特别,并且会丢弃所有原本不是他的东西。所以这是个问题。

那么正确的做法是什么?

ChromeWebBrowser控件

现在我将与您分享一种未记录的处理弹出窗口的方法。说到 ChromeWebBrowser 控件,我们知道它是如何创建网络浏览器的,这对我们很有帮助,而实际上它并没有。该控件仅承载 webbrowser 窗口句柄。它有一个名为 managedCefBrowserAdapter(ManagedCefBrowserAdapter 类)的私有字段,用于处理 Web 浏览器的实际创建。 ChromiumWEBBrowser 实现了 IWebBrowserInternal,它有一个方法 OnAfterBrowserCreated 和一个 IBrowser 类型的参数。然后该控件调用browser.GetHost().GetWindowHandle() 来获取它所托管的网络浏览器的实际窗口句柄(HWND)。挺好的。

ChromeWebBrowser 的问题在于它没有接受 IBrowser 作为参数的构造函数。它只有接受 HtmlString、字符串和 IRequestContext 参数的构造函数。这些控制等待 调用 OnHandleCreated(一个基类覆盖),它调用 managedCefBrowserAdapter.CreateBrowser 之后它等待直到调用 IWebBrowserInternal 的 OnAfterBrowserCreated 的实现。

再说一遍,什么是有效的方法?

现在,这种实际可行的方法是长期反复试验的产物。不过要注意的一点是,我不知道它为什么起作用以及如何起作用,但我知道它起作用。

首先,我没有使用 ChromeWebBrowser。但是我复制了它的代码,省略了它创建 .net 控件的部分。在这种情况下,我将浏览器的窗口句柄 (HWND) 定位为由任何公开 HWND 的对象托管。显然我创建了一个使用修改后的代码的类(NativeCefWebBrowser)。 ChromeWebBrowser 的原始构造函数仍然存在,因为它们用于创建父 Web 浏览器。但是我添加了一个接受以下参数的构造函数:ICefBrowserParent parent(我创建的一个接口和 IBrowser 浏览器,它接收 ILifeSpanHandler 的 OnBeforePopup 中的浏览器参数。我还添加了一个公共方法 AttachBrowser,它有一个接收 IBrowser 的单个参数 IBrowser ILifeSpanHandler 的 OnAfterCreated 中的参数。它将由 CefNativeWebBrowser 类保留的浏览器。

为什么我不保留从 ILifeSpanHandler.OnBeforePopup 接收的浏览器实例,而是使用从 ILifeSpanHandler.OnAfterCreated 接收的实例,而它们是同一个浏览器实例?这是我不知道为什么的那些部分之一。我注意到的一件事是,当我在 ILiffeSpanHandler.OnBeforePopup 期间调用 browser.GetHost().GetWindowHandle() 时,我收到的第一个窗口句柄与我在 ILifeSpanHandler.OnAfterCreatd 期间调用该方法时不同。正因为如此,我将浏览器实例从我传递给 NativeCefWebBrowser.AttachBrowser 的后者中存储起来,以便妥善保管。

在 NativeCefWebBrowser(ICefBrowserParent parent, IBrowser browser) 构造函数中,我将以下私有字段设置为 true:browsercreated、browserinitialized(chromewebbrwoser 原始字段)和 isAttachingBrowser(添加了私有字段)。您不会在此构造函数中调用 ManagedCefBrowserAdapter 的 CreateBrowser,而是通过浏览器实例调用其 OnAfterBrowserCreated。您在此构造函数中不需要太多,因为您将等待 ILifeSpanHandler 实现器向您传递它将在 OnAfterCreated 方法期间接收的浏览器实例。请注意,在调用 ManagedCefBrowserAdapter 的 OnAfterBrowserCreated 方法时,ManagedCefBrowserAdapter 仍然会调用 OnAfterBrowserCreated 的 IWebBrowserInternal 实现,如果发生这种情况,您必须在 isAttachingBrowser 为 true 时立即退出,因为以下代码将不再有意义。

调用 NativeCefWebBrowser(ICefBrowserParent, IBroser) 构造后,您可以正常设置事件监听器。

就是这样。

以下是我写的部分代码

ICefBrowserParent 界面

public interface ICefBrowserParent
{
    IntPtr Handle { get; }
    Size ClientSize { get; }
    bool Disposing { get; }
    bool IsDisposed { get; }
    bool InvokeRequired { get; }
    IAsyncResult BeginInvoke(Delegate d);
    object Invoke(Delegate d);
    event EventHandler Resize;
}

您会注意到,该接口中的方法、属性和事件已经由 System.Windowns.Forms.Control 类实现。因此,如果您从继承 Control 类的类中实现它,则不再需要实现它。该接口仅适用于非Control类。

class NativeCefWebBrowser
{
    public NativeCefWebBrowser(ICefBrowserParent, IBroser)
    {
        requestContext = browser.GetHost().RequestContext;
        this.parent = parent; // added field
        HasParent = true;     // IWebBrowserInternal. I don't know what's this for
        mustSetBounds = true; // added field
        browserCreated = true;
        isAttachingBrowser = true; // added field
        InitializeFieldsAndCefIfRequired();
        managedCefBrowserAdapter.OnAfterBrowserCreated(browser);
    }
}

ILifeSpanHandler.OnBeforePopup(..., out IWebBrowser newWebBrowser)
{
    CefNativeWebBrowser b = new CefNativeWebBrowser
    (
        parent, // defined else where
        browser
    );
    // Attach event handlers
    b.TitleChanged...;
    newWebBrowser = b;
}

ILifeSpanHandler.OnAfterCreated(...)
{
    ((CefNativeWebBrowser)webBrowser).AttachBrowser(browser);
}

【讨论】:

  • 如果我没看错,但我不确定我是不是因为你的例子不完整,你设置 HasParent = true 的事实可能会导致返回不正确的 IBrowser 实例在某些情况下。对于弹出窗口,不会故意调用事件,如果希望调用事件,您可以使用处理程序 cefsharp.github.io/api/86.0.x/html/… cefsharp.github.io/api/86.0.x/html/T_CefSharp_ILoadHandler.htm,然后作为处理程序实现中的一种解决方法将 IWebBrowser 转换为 IWebBrowserInternal 并为每个事件调用 set 方法。
  • 如果您希望看到添加支持的用例,欢迎转到github.com/cefsharp/CefSharp/issues/new/choose 并选择功能请求。
  • @amaitland 正如我所说,我不知道那有什么用。我也将它设置为 false,我没有发现任何区别。我在我的帖子中包含它的一个原因是有人对其使用做一些明确的解释,因为到目前为止,还没有 IWebBrowserInternal 接口的文档,或者我只是找不到它。但我已经用谷歌搜索了很多次。关于不是在弹出窗口上故意调用的事件,不,它们是。使用我发布的实现,调用了 LoadEnd、LoadStart、AddressChanged 等。
  • 没有文档,内部使用这些方法,查看ClientAdapter的源码。源代码在 GitHub 上。我强烈反对您的方法,因为当涉及到您传递的 IBrowser 实例时,您可能会得到意想不到的结果。我建议一种更简洁、更易于理解且不太可能中断的解决方法,因为它不依赖于内部实现。如果您从版本 86 升级到最新的 87 预发行版,您的代码将无法编译。如果您的解决方案适合您,那太好了,我强烈建议其他人不要尝试这样做。这是我的看法。
  • 我并不坚持我的实验结果。人们可能会也可能不会使用它。 CefSharp 作者也可以使用或不使用它。到目前为止,我关心的是让 CefSharp 以我喜欢的方式为我工作。我实际上并不知道 CefSharp 已经存在。在我在谷歌搜索栏中输入:embeddable google chrome browser 并按回车键之前,我仍在使用永远不会长大的 Windows 浏览器 ActiveX 控件。
猜你喜欢
  • 2013-02-03
  • 2013-05-04
  • 1970-01-01
  • 1970-01-01
  • 2021-01-11
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多