首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >使用CefGlue从Url返回HTML页面

使用CefGlue从Url返回HTML页面
EN

Stack Overflow用户
提问于 2016-04-27 05:19:17
回答 1查看 1.4K关注 0票数 0

我试图为以下(原型)方法编写一个实现:

代码语言:javascript
复制
var result = browser.GetHtml(string url);

我之所以需要这样做,是因为有许多页面将大量Javascript推送到浏览器,然后Javascript呈现页面。可靠地检索此类页面的唯一方法是在检索结果HTML之前允许Javascript在浏览器环境中执行。

我目前的尝试是使用CefGlue。在下载这个项目并将其与这个答案中的代码相结合之后,我得到了以下代码(为了完整起见,请在这里列出):

代码语言:javascript
复制
using System;
using System.Diagnostics;
using System.Drawing;
using System.Drawing.Imaging;
using System.Drawing.Printing;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using Xilium.CefGlue;

namespace OffScreenCefGlue
{
    internal class Program
    {
        private static void Main(string[] args)
        {
            // Load CEF. This checks for the correct CEF version.
            CefRuntime.Load();

            // Start the secondary CEF process.
            var cefMainArgs = new CefMainArgs(new string[0]);
            var cefApp = new DemoCefApp();

            // This is where the code path divereges for child processes.
            if (CefRuntime.ExecuteProcess(cefMainArgs, cefApp) != -1)
            {
                Console.Error.WriteLine("CefRuntime could not create the secondary process.");
            }

            // Settings for all of CEF (e.g. process management and control).
            var cefSettings = new CefSettings
            {
                SingleProcess = false,
                MultiThreadedMessageLoop = true
            };

            // Start the browser process (a child process).
            CefRuntime.Initialize(cefMainArgs, cefSettings, cefApp);

            // Instruct CEF to not render to a window at all.
            CefWindowInfo cefWindowInfo = CefWindowInfo.Create();
            cefWindowInfo.SetAsOffScreen(IntPtr.Zero);

            // Settings for the browser window itself (e.g. should JavaScript be enabled?).
            var cefBrowserSettings = new CefBrowserSettings();

            // Initialize some the cust interactions with the browser process.
            // The browser window will be 1280 x 720 (pixels).
            var cefClient = new DemoCefClient(1280, 720);

            // Start up the browser instance.
            string url = "http://www.reddit.com/";
            CefBrowserHost.CreateBrowser(cefWindowInfo, cefClient, cefBrowserSettings, url);

            // Hang, to let the browser do its work.
            Console.Read();

            // Clean up CEF.
            CefRuntime.Shutdown();
        }
    }

    internal class DemoCefApp : CefApp
    {
    }

    internal class DemoCefClient : CefClient
    {
        private readonly DemoCefLoadHandler _loadHandler;
        private readonly DemoCefRenderHandler _renderHandler;

        public DemoCefClient(int windowWidth, int windowHeight)
        {
            _renderHandler = new DemoCefRenderHandler(windowWidth, windowHeight);
            _loadHandler = new DemoCefLoadHandler();
        }

        protected override CefRenderHandler GetRenderHandler()
        {
            return _renderHandler;
        }

        protected override CefLoadHandler GetLoadHandler()
        {
            return _loadHandler;
        }
    }

    internal class DemoCefLoadHandler : CefLoadHandler
    {
        public string Html { get; private set; }

        protected override void OnLoadStart(CefBrowser browser, CefFrame frame)
        {
            // A single CefBrowser instance can handle multiple requests
            //   for a single URL if there are frames (i.e. <FRAME>, <IFRAME>).
            if (frame.IsMain)
            {
                Console.WriteLine("START: {0}", browser.GetMainFrame().Url);
            }
        }

        protected override async void OnLoadEnd(CefBrowser browser, CefFrame frame, int httpStatusCode)
        {
            if (frame.IsMain)
            {
                Html = await browser.GetSourceAsync();
                Console.WriteLine("END: {0}, {1}", browser.GetMainFrame().Url, httpStatusCode);
            }
        }
    }

    internal class DemoCefRenderHandler : CefRenderHandler
    {
        private readonly int _windowHeight;
        private readonly int _windowWidth;

        public DemoCefRenderHandler(int windowWidth, int windowHeight)
        {
            _windowWidth = windowWidth;
            _windowHeight = windowHeight;
        }

        protected override bool GetRootScreenRect(CefBrowser browser, ref CefRectangle rect)
        {
            return GetViewRect(browser, ref rect);
        }

        protected override bool GetScreenPoint(CefBrowser browser, int viewX, int viewY, ref int screenX, ref int screenY)
        {
            screenX = viewX;
            screenY = viewY;
            return true;
        }

        protected override bool GetViewRect(CefBrowser browser, ref CefRectangle rect)
        {
            rect.X = 0;
            rect.Y = 0;
            rect.Width = _windowWidth;
            rect.Height = _windowHeight;
            return true;
        }

        protected override bool GetScreenInfo(CefBrowser browser, CefScreenInfo screenInfo)
        {
            return false;
        }

        protected override void OnPopupSize(CefBrowser browser, CefRectangle rect)
        {
        }

        protected override void OnPaint(CefBrowser browser, CefPaintElementType type, CefRectangle[] dirtyRects, IntPtr buffer, int width, int height)
        {
            // Save the provided buffer (a bitmap image) as a PNG.
            var bitmap = new Bitmap(width, height, width*4, PixelFormat.Format32bppRgb, buffer);
            bitmap.Save("LastOnPaint.png", ImageFormat.Png);
        }

        protected override void OnCursorChange(CefBrowser browser, IntPtr cursorHandle)
        {
        }

        protected override void OnScrollOffsetChanged(CefBrowser browser)
        {
        }
    }

    public class TaskStringVisitor : CefStringVisitor
    {
        private readonly TaskCompletionSource<string> taskCompletionSource;

        public TaskStringVisitor()
        {
            taskCompletionSource = new TaskCompletionSource<string>();
        }

        protected override void Visit(string value)
        {
            taskCompletionSource.SetResult(value);
        }

        public Task<string> Task
        {
            get { return taskCompletionSource.Task; }
        }
    }

    public static class CEFExtensions
    {
        public static Task<string> GetSourceAsync(this CefBrowser browser)
        {
            TaskStringVisitor taskStringVisitor = new TaskStringVisitor();
            browser.GetMainFrame().GetSource(taskStringVisitor);
            return taskStringVisitor.Task;
        }
    }
}

代码的相关部分如下:

代码语言:javascript
复制
protected override async void OnLoadEnd(CefBrowser browser, CefFrame frame, int httpStatusCode)
{
    if (frame.IsMain)
    {
        Html = await browser.GetSourceAsync();
        Console.WriteLine("END: {0}, {1}", browser.GetMainFrame().Url, httpStatusCode);
    }
}

这实际上是可行的;您可以使用调试器检查Html变量,其中有一个HTML页面。问题是,Html变量在回调方法中对我没有好处;它将三层深埋在类层次结构中,我需要在不创建Schroedinbug的方法中返回它。

(试图从该string Html属性获得结果,包括尝试使用调试器中的HTML可视化工具查看它,似乎会导致死锁,这是我真正想要避免的,特别是因为这段代码将在服务器上运行)。

如何安全可靠地实现我的var result = browser.GetHtml(string url);

的额外问题:可以使用这一技术将上述代码中的回调机制转换为任务吗?那会是什么样子?

EN

回答 1

Stack Overflow用户

发布于 2016-04-27 07:11:06

请记住,当前的CefGlue版本没有提供任何同步上下文,所以在大多数情况下,您不应该在回调中使用异步/等待,除非您确定要做什么。

“可靠”代码应该是异步的,因为大多数CEF调用都是异步的(不管是否提供回调)。异步/等待大大简化了这个任务,所以我假设这个问题可以简化为:“如何正确地编写GetSourceAsync方法?”这也取决于您的额外问题,简单的答案当然是否定的,这一技术应该被认为是有害的,因为如果不了解底层代码会导致不同的效果。

因此,不管GetSourceAsync方法,特别是TaskStringVisitor,我只建议您永远不要直接执行TaskCompletionSource的方法,因为它同步执行连续(在.NET 4.6中,它可以选择异步执行连续,但我个人没有检查它是如何在内部执行的)。这是需要尽快释放一个CEF线程。否则最终你会得到大的延续树,循环或等待,什么实际上是永远阻止浏览器的线程。另外,请注意,这种扩展也是有害的。,因为他们有相同的问题,上面描述-唯一的选择是有真正的异步延续。

代码语言:javascript
复制
protected override void Visit(string value)
{
    System.Threading.Tasks.Task.Run(() => taskCompletionSource.SetResult(value));
}

有些CEF是混合的:如果我们已经不在所需的线程上,它们会将任务排队到所需的线程,或者同步执行。对于这种情况,应该简化处理,在这种情况下最好避免异步处理。同样,为了避免同步延续,因为它们可能导致重入问题和/或只是获得不必要的堆栈帧(希望只在短时间内,代码不会停留在某个地方)。

最简单的示例之一是,但对于其他一些API调用也是如此:

代码语言:javascript
复制
internal static class CefTaskHelper
{
    public static Task RunAsync(CefThreadId threadId, Action action)
    {
        if (CefRuntime.CurrentlyOn(threadId))
        {
            action();
            return TaskHelpers.Completed();
        }
        else
        {
            var tcs = new TaskCompletionSource<FakeVoid>();
            StartNew(threadId, () =>
            {
                try
                {
                    action();
                    tcs.SetResultAsync(default(FakeVoid));
                }
                catch (Exception e)
                {
                    tcs.SetExceptionAsync(e);
                }
            });
            return tcs.Task;
        }
    }

    public static void StartNew(CefThreadId threadId, Action action)
    {
        CefRuntime.PostTask(threadId, new CefActionTask(action));
    }
}

更新:

这实际上是可行的;您可以使用调试器检查Html变量,其中有一个HTML页面。问题是,Html变量在回调方法中对我没有好处;它将三层深埋在类层次结构中,我需要在不创建Schroedinbug的方法中返回它。

您只需实现CefLifeSpanHandler,就可以在创建CefBrowser (异步创建)之后直接访问它。存在CreateBrowserSync调用,但不是预先设置的方式。

PS:我正在CefGlue下一代的路上,但是现在还没有什么可以使用的。计划进行更好的异步/等待集成。我个人正在集中地使用异步/等待的东西,就在服务器端环境中。

票数 1
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/36880910

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档