使用CEFSharp ILifeSpanHandler接口处理弹出窗口

时间:2018-12-14 18:15:40

标签: cefsharp chromium-embedded

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

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

我没有代码,因为我的问题是“如何做”。在OnBeforePopup中,我检查了targetUrl,但是似乎已经准备好进行装饰了,因为我读到它始终没有实现。我还查看了browner / chromiumWebBrowser对象,浏览器和newBroswer似乎一无是处。有人希望在OnAfterCreatedchromiumWebBrowser中返回一个对象,但在我看来,它什么都没有。

我正在测试以下内容

        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

1 个答案:

答案 0 :(得分:-1)

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

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

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

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

那么正确的方法是什么?

ChromeWebBrowser 控件

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

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

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

现在,这种真正有效的方法是长期反复试验的产物。但需要注意的是,我不知道它为什么以及如何工作,但我知道它有效。

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