登录 用户中心() [退出] 后台管理 注册
   
您的位置: 首页 >> CLQ工作室开源代码 >> 主题: [CLQ工作室CEF项目]之CEF超详细接口使用手册     [回主站]     [分站链接]
标题
[CLQ工作室CEF项目]之CEF超详细接口使用手册
clq
浏览(1066) + 2019-04-19 23:28:57 发表 编辑

关键字:

[2023-05-29 06:36:51 最后更新]
[CLQ工作室CEF项目]之CEF超详细接口使用手册

--------------------------------------------------
文中所涉及代码:
dcef3 来自
https://github.com/hgourvest/dcef3

--------------------------------------------------
目录
序言.说明
问题1:浏览器的图片另存为右键菜单怎样实现?
问题2:浏览器如何真正实现单进程?
问题3:如何关闭gpu硬件加速及cef命令行参数如何传递?
附注1:如何让 dcef3 和 CEF4Delphi 共存
问题4:为何直接设置 html 源码时不工作?
附注2:dcef3的2623版本出错时,重新 build 能解决大部分的问题
问题5:如何让浏览器访问某个地址不跳转?
附注3:delphi7下的独立窗口中有浏览器控件的话要在程序退出前释放。
附注4:delphi7下的独立窗口中有浏览器控件的话,嵌入另外一个 TForm 时异常要在窗体 show 之后再创建 cef 控件放到窗体上。
附注5:dcef3 在 delphi7 中调用 GetMainFrame 仍然异常的处理。怎样判断 MainFrame 可以操作了?
附注6:关于 附注4 的修正
问题6:如何设置代理?及 dcef3 下为何最好是禁用代理?
问题7:如何在问题1中弹出文件另存为的对话框?
问题8:lazarus 下如何使用 dcef3?
答:请参阅 http://newbt.net/ms/vdisk/show_bbs.php?id=32DE27459B7235DFBBD850C71B9D0183&pid=164
问题9:cef2623 版本乱码的原因之一,不支持 https-equiv ,怎么办?
问题10:dcef3 无法直接修改 CefSettings 怎么办?

代理及单进程模式下再加速
多进程模式下传递命令行参数的大坑及方法,附带一个"黑屏"解决方法(传递的硬件开启参数要一致)
如何在下载图片时指定 ref 头信息

--------------------------------------------------

CEF 接口或说是类似开源浏览器接口我早在近20年前刚进入业界不久就开始研究了.说实在的,所谓开源其实有时候和没开也没多大区别 -- 因为接口设计和实现的原因,这些接口都超级复杂.
记得那是改造一个基于 IE 控件的修改过程中,感觉 IE 的控件做着做着很没意思 -- 因为太多的东西不能做了.转而研究起当时一个开源的 activex 的仿造 ie 接口的程序,我记得它好象是和 firefox
共内核的,但令人失望的是它的接口虽然拼命的靠近 IE 控件了,但仍然很难用,而且还不如 IE 的控件. 过了大概一两年吧,我当时在 UT 旗下某个公司做 leader 时间比较多了,为了不打瞌睡,我编译了
firefox 的源码,现在大家都知道 chrome 源码的难编译,我当时编译 firefox 时就领教了,所以我从来没想过要去编译 chrome 的源码. 不过说实在的,这在开源界里还算好的了,至少我在 windows 下只用
vc 就编译出来了,大概只花了一个下午就搞定了.那时候我觉得是我最难编译的项目之一了,之所以用之一是因为它可以和我当时编译的嗅探器源码稍好一点. (在这里我要很不情愿的坦白,我觉得最难编译的是
GTK的完整源码,我前前后后断断续续花了三年还是五年才完整通了一个 gtk windows 项目,还不是全代码 ... 感觉真是丢脸,直到多年后我发现有 windows下的预编译完整版本后才知道原因:那么多的依赖项目,
它们各自属于不同的开源项目,这也还罢了,各个版本间还有不兼容的情况!想要个完整版,你首先就得找到它们当时在 gtk 中使用时的那个版本 ... 要想自己收集太难了.更何况这基本上就相当于把整个开源界的著名项目
都编译了一遍,还是在 windows 上,为什么要强调是在 windows 上 -- 因为有的项目根本没有 windows 版本,你还得自己做完移植工作)...

虽然说了一堆好象没用的抱怨的话,这其实对于大家理解 cef 项目也还有有帮助的 -- 因为这道出了很多基于 cef 的小开源项目都会长时间暂停跟踪最新的 cef 版本源码,而偏向非全功能的应用.这些项目通常都会说明自己
当前的 cef 版本,或者是说明怎样让使用者自己更新最新的 cef 源码. 原因无它,因为太难编译了,同时如果不是纯做浏览器得到的好处又实在不怎么大,所以升级动力小过阻力 ... 当然了,我还没编译过 cef 源码不知道到底难
到什么程度,反正我就当它象 firefox 那么难不去理它好了.

扯得有点远,继续前面的浏览器控件经历,后来回到桂林后因为一个比较特殊的原因我发现了当时还不是太出名的 cef 项目,据说当时 QQ 用 cef 做界面了,大概 09 年吧.但试用之下很是失望,因为太容易崩溃了,我还是用 C++
源码编译的,可不是什么第三方控件.而且接口当时就觉得太奇葩,与 ie 控件相比难用太多了(今天也是,所以才会有那么多的封装库). 多年以后我才在一家主要用 delphi 的公司中碰到一个稳定版本的 cef -- 当然现在的 cef 项
目都很稳定(当然指我接触到的).

说了这么多,我想说的是即便是在今天关于 cef 的使用仍然是比较麻烦的,原因一为是接口太多,二是接口有些设计上确实不太合理,另外我个人觉得接口虽然多,其实有很多功能还是不能做,感觉还是比较失望的.现在开源项目中
主要是将其做为界面的跨平台实现来做,反而浏览器相关的资料极度的少. 但我个人还是喜欢高订制又不用重编译的浏览器应用,这在现在的环境是做不了的,看着现在国内各种弄得极度花的浏览器,心跳极度的失望,更可怕的是
这其实已经影响到国外的浏览器,firefox,chrome,微软新的几个浏览器全部都花到极点,不花一番功夫配置一下你能给各个软广告烦到死. 好了,不管怎么说吧,我们这个项目就是要记录下做一个全功能浏览器时用得到的那些接口.
不能全部说,因为那等于没说,而且我们也不全懂... 也不能只把网上那些已有的收集一下,因为那离一个正常的浏览器差得很远. 在目前网上已有的资料中最缺少的就是各种浏览器常用右键功能的实现方法,比如"另存图片"菜单
怎样实现? 我找了很久很久就是没看到 ... 因为根本没有直接的方法,而且涉及到好几个 OLE(没错 windows 下还是借用了 ole 的方法,我个人觉得这是 cef 接口最失败的地方) 接口相互操作.我主要先用比较简单的 delphi 实现
来做讲解,因为它的项目调试编译都最简单,而且各接口命名和官方接口几乎没有差别,比 C# 版本那些要更容易看一些(当然 C# 版本使用更简单).

好了,第一天我就给大家送上一个大礼吧,先说说图片另存的实现方法,我敢 99.99% 的肯定这至少是国内第一份中文版的教程,我找到的菜单订制中至少有一大半是问怎样取消右键的 -- 因为大多数用户是拿来做界面.做浏览器的人
是有,可惜都是自家浏览器中要保留的技术吧.

问题1:浏览器的图片另存为右键菜单怎样实现?
在 cef 控件中和 ie 控件完全不同,它的很多功能实际上是要开发者自己二次开发的,它只提供了接口,做就让一些从 ie 控件转过来的同学很不适应,感觉很浪费时间精力.确实也是如此,我个人也觉得很多常用功能完全没有必要这样做.
在 cef 中要实现图片另存为菜单首先要知道怎样订制右键菜单,然后还要找到另存的接口(没法知道这是要用接口还是用什么吧,这就是 cef 的失败之处,如果是 ie 控件我们几乎可以肯定不是控件的函数中使用就是在事件中实现,cef
则分散在各种接口中),然后还要知道右键操作的图片是哪个? 要找到 dom 还是什么事件中可以取到?好了吐槽还是公布答案吧:

1.1 首先在 BeforeContextMenu 事件中加入菜单项目,加入项目时要象早期的 VC 菜单一样订好菜单的命令 ID ,然后要在另外一个事件中根据这个命令 ID 执行不同的操作.
要加入的菜单是在事件中的一个 CefMenuModel 的函数来加入的,示例如下:

procedure TMainForm.crmBeforeContextMenu(Sender: TObject;
const browser: ICefBrowser; const frame: ICefFrame;
const params: ICefContextMenuParams; const model: ICefMenuModel);
begin
model.AddItem(CUSTOMMENUCOMMAND_INSPECTELEMENT, 'Inspect Element'); //clq 2019 这里加入了一个右键菜单
//model.Clear(); //clq 这样就可以清除右键菜单了

model.AddItem(100, '在新窗口中打开...'); //clq 2019 这里加入了一个右键菜单

瞧,我还附送了一个大家最想知道的右键菜单清空的方法.

1.2 在 ContextMenuCommand 事件中响应这个菜单项.函数中的 commandId 参数就是你前面 model.AddItem() 中的第一个参数.
示例:
procedure TMainForm.crmContextMenuCommand(Sender: TObject;
const browser: ICefBrowser; const frame: ICefFrame;
const params: ICefContextMenuParams; commandId: Integer;
eventFlags: TCefEventFlags; out Result: Boolean);
var
mousePoint: TCefPoint;
url:string;
begin
Result := False;
if (commandId = 100) then
begin
......

1.3 好了,那怎么将一个图片下载下来保存到我们指定的磁盘位置呢?
我一度觉得要自己实现 http 下载,事实上确实可以自己实现,不过至少现在的 cef 版本有默认的处理方式,就是提供一个函数输入要下载的地址,然后会在下载过程回调某个事件函数让你可以在界面中显示
下载的进度. 这个完整步骤其实有点多 -- 相当多,所以今天肯定是说不完的,只能先简述.大概就是取浏览器控件的 host 然后调用它的一个函数,没用过 cef 的同学一定大惊这怎么整个 host 出来了,这就是
cef 里的一个重要概念了,它实际上不只是一个单独的浏览器控件对象,根据不同的功能和场景又分为多个其他的对象(实现为接口)来负责.比如这里的 host ,另外常用的还有 frame 而 frame 还是一个集合
这时候还得和一个叫 mainframe 的东东打交道.
够了,在 delphi 里这代码为
crm.Browser.Host.StartDownload(url);

可以看到实际上它是统一的另存接口,并不是专门来存图片的.
这个 host 在 delphi 版中为 ICefBrowserHost 在其他版本和官方文档中用 CefBrowserHost 很容易搜索到,只是要从从浏览器控件中取到它又得费一番周折...

1.4 最后怎样得到图片的地址呢?
其实前面的接口至少在 delphi 版中还比较容易找到,当前操作的图片怎样取得呢? 幸好我也断断续续做过些前端工作,知道十有八九要先找到当前操作的 node 节点,不知道什么是 node 节点的同学...
我无话可说... 估计 cef 接口不适合你,真的. 我找啊找啊,最后在 electron 文档中找到了一点端倪,
https://electronjs.org/docs/api/web-contents

刚好我前段做过 webgui 的选型,通过 HasImageContents 关键字找到它后我知道,它们很多东西是通用的,果然 electron 的文档比官方的清晰得多,我一眼就看到了 Event: 'context-menu' 事件中的 srcURL 参数
做过前端的都知道图片的地址就在 src 属性中啊! 会不会是这个参数,一试之下果然不错. 这里要同情一下 C# 的用户, C# 的文档
https://cefsharp.github.io/api/63.0.0/html/T_CefSharp_IContextMenuParams.htm
中就没有这个关键字,原因是它自作聪明的改成了 SourceUrl 这个参数名 ... 唉,所以说人太过聪明也不是什么好事.
当然了,也不一定,在 delphi 里它也叫 SourceUrl ... 官方文档
https://magpcss.org/ceforum/apidocs3/projects/(default)/CefContextMenuParams.html
中也是这个名称 ... GetSourceUrl 所以这次自作聪明的是 electron,这从一个侧面表明了为什么 electron 为何能在众多 cef 封装中脱颖而出.

好了 delphi 的完整代码为:
procedure TMainForm.crmContextMenuCommand(Sender: TObject;
const browser: ICefBrowser; const frame: ICefFrame;
const params: ICefContextMenuParams; commandId: Integer;
eventFlags: TCefEventFlags; out Result: Boolean);
var
mousePoint: TCefPoint;
url:string;
begin
Result := False;
if (commandId = CUSTOMMENUCOMMAND_INSPECTELEMENT) then
begin
mousePoint.x := params.XCoord;
mousePoint.y := params.YCoord;
//Splitter.Visible := True;
DevTools.Visible := True;
Splitter.Visible := True; //clq 2019 要先显示开发板再显示分隔条
actDevTool.Checked := True;
DevTools.CloseDevTools(crm.Browser);
application.ProcessMessages;
DevTools.ShowDevTools(crm.Browser,@mousePoint);
Result := True;
end;

if (commandId = 100) then
begin
url := params.LinkUrl;

ShowMessage(url);
//ShowMessage(BoolToStr(params.HasImageContents, True)); //应该是 bug ,一直是 true
params.MediaType; //指示选中(点击)的节点是什么类型,可准确判断是否是图片

//crm.Browser.Host.StartDownload();
ShowMessage(params.SourceUrl);
crm.Browser.Host.StartDownload(params.SourceUrl);

Result := True;
end;
end;

--------------------------------------------------
小结:
暂时会以问答的形式提供各种自定义全功能浏览器时要做的常用方法,常用的功能其实对于一个浏览器来说并不多,这个文档应该不会太大.
改天,会集结放到 github 上,以便大家一上手就可以立即得到一个象 ie 浏览器控件一样的全功能 cef .


clq
2019-04-20 18:26:21 发表 编辑

问题2:浏览器如何真正实现单进程?
使用了 cef 的程序会默认使用多进程模式,这时你会惊奇地发现,打开一个程序会在任务管理器里看到好几个! 这在很多场合下是不合适的. 其实 cef 本身就有选项是可以切换
是否为多进程模式的. 这里有个要注意的情况,一般来说多进程模式分开了通讯,渲染等等,据说是可以让崩溃的情况减少,不过根据我的实际测试,实际上并不会让崩溃情况更好.
会千万崩溃的页面,对于是否多进程都是一样会崩溃的. 这至少说明通过这种方式来想避免崩溃恐怕是行不通的.恐怕要自己实现不同页面不同进程才能实现 firefox 那样的标签
崩溃警告 -- 不过 frefox 的崩溃我仍然是经常碰到的. 这里要特别提一下,我发现手头上的经典 3.2623 版本在 win10 下的崩溃的页面,放到 xp 下访问却是正常的! 看来这个
版本作为 chrome 在 xp 上的最后一个版本是有道理的.

又扯多了,要实现真正的单进程模式网上的很多说法都是错误的,实际上 delphi 版本控件中,只要修改全局变量
CefSingleProcess := True
就可以了.这个变量最终会在 cef_initialize() 函数中的第二个参数的 settings.single_process 中起作用.

但是这样设置的话十有八九会让你的 cef 程序慢到无法忍受,估计很多同学又会改回去. 其实这是因为 cef 的 ui 消息循环刷新减慢了,自己手工多调用几次就可以了.
这个消息循环函数为 CefDoMessageLoopWork ,我在一个国内著名浏览器开发人员的 csdn 的个人 blog 上也看到过对这个函数的说明.大家可以搜索这个关键字来更详细的了解
一下,当然至少目前国内对这个函数是很少有人明白及提起的.

那么怎么自己调用呢? 对于我手头的 dcef3 来说是一个定时器,只要修改定时器间隔就可以了,根据我的测试在现代的 cpu 机器加 win10 系统上其实默认值也工作得很好,在我的
一台老机器的 win7 上就要将间隔调整到最小,代码如下(大家可以自己调整,根据自己的环境选择合适的参数)
    //CefTimer := SetTimer(0, 0, 10, @TimerProc);  //clq 2019 好象和这个无关,xp 下旧机器也是相当快的
    CefTimer := SetTimer(0, 0, 1, @TimerProc);   //clq 2019 不知道有用不




clq
2019-04-20 18:42:54 发表 编辑

问题3:如何关闭gpu硬件加速及cef命令行参数如何传递?
关闭 gpu 硬件加速,我随手找到了这篇文章 https://www.cnblogs.com/lynnjeans/p/8735240.html
作者提到使用命令行参数来关闭.方法是

--------------------------------------------------
第一种是在程序快捷方式中加command-line flag: --disable-gpu --disable-gpu-compositing, reference:https://bitbucket.org/chromiumembedded/cef/issues/1480/off-screen-rendering-problem-on-windows-7

clickonce发布的程序无法设置快捷方式,所以:

第二种:在代码中设置:
var settings = new CefSettings();

//NOTE: The following function will set all three params
//settings.SetOffScreenRenderingBestPerformanceArgs();
settings.CefCommandLineArgs.Add("disable-gpu", "1");
settings.CefCommandLineArgs.Add("disable-gpu-compositing", "1");
settings.CefCommandLineArgs.Add("enable-begin-frame-scheduling", "1");

settings.CefCommandLineArgs.Add("disable-gpu-vsync", "1"); //Disable Vsync

//Disables the DirectWrite font rendering system on windows.
//Possibly useful when experiencing blury fonts.
settings.CefCommandLineArgs.Add("disable-direct-write", "1");
--------------------------------------------------
要说 chrome 的命令行参数,那确实和 firefox 的命令行参数一样非常的有用.能够在代码中使用那真是太好了. 但我手头的版本对于 settings 的接口并没有封装这个命令行的接口,不过通过查找
CommandLine 关键字我发现可以通过回调函数来实现.具体做法是定义一个函数来代替 CefOnBeforeCommandLineProcessing ,然后就可以在其函数的 ICefCommandLine 参数中修改了.
具体代码为:





//clq add 为了加禁用 GPU 硬件加速,要使用命令行参数
procedure BeforeCommandLineProcessing(const processType: ustring; const commandLine: ICefCommandLine);
begin
  //https://www.cnblogs.com/lynnjeans/p/8735240.html

 //NOTE: The following function will set all three params
//settings.SetOffScreenRenderingBestPerformanceArgs();
//settings.CefCommandLineArgs.Add("disable-gpu", "1");
//settings.CefCommandLineArgs.Add("disable-gpu-compositing", "1");
//settings.CefCommandLineArgs.Add("enable-begin-frame-scheduling", "1");
//
//settings.CefCommandLineArgs.Add("disable-gpu-vsync", "1"); //Disable Vsync
//
////Disables the DirectWrite font rendering system on windows.
////Possibly useful when experiencing blury fonts.
//settings.CefCommandLineArgs.Add("disable-direct-write", "1");

  //--------------------------------------------------
  //delphi 版本中没找到怎样直接修改 setting 中的命令行参数的办法,但是有个回调函数
  //"chrome://flags/" 这个地址似乎可以查看配置是否成功
  //"chrome://chrome-urls" 在地址栏输入上述文字,会出现很多连接,chrome所有功能都罗列出来//但这个版本不行,和前面的那个一样都会跳到 "chrome://version/"
  ////commandLine.AppendArgument();

  //NOTE: The following function will set all three params
  //settings.SetOffScreenRenderingBestPerformanceArgs();
  commandLine.AppendSwitchWithValue('disable-gpu', '1');
  commandLine.AppendSwitchWithValue('disable-gpu-compositing', '1');
  commandLine.AppendSwitchWithValue('enable-begin-frame-scheduling', '1');

  commandLine.AppendSwitchWithValue('disable-gpu-vsync', '1'); //Disable Vsync

  //Disables the DirectWrite font rendering system on windows.
  //Possibly useful when experiencing blury fonts.
  commandLine.AppendSwitchWithValue('disable-direct-write', '1');

end; 


begin
  CefCache := 'cache';
  CefOnRegisterCustomSchemes := RegisterSchemes;

  CefOnBeforeCommandLineProcessing := BeforeCommandLineProcessing; //clq add 为了加禁用 GPU 硬件加速,要使用命令行参数
...

要注意的是,这一段代码要写在程序最开始启动的地方.
那么怎么查看是否已经起效果了呢? 方法是用 cef 控件来访问
chrome://flags/
来确认.不过不同版本的 cef 访问后的结果大相径庭. 另外这只能确认命令行参数是否已成功,至于是否硬件加速确实禁止了恐怕要另外想办法.


clq
2019-04-27 15:54:03 发表 编辑

附注1:如何让 dcef3 和 CEF4Delphi 共存

严格来说这个和 cef 本身没有关系,但这毕竟是当前 delphi 语言下仅有的两种流行的 cef 封装,所以考虑再三还是作为附注加入进来.
解决办法说赤也简单,首先这种冲突只是由于相同的类名造成的,但并不需要像其他语言那样更改所有同名的类.其实只要保证窗体设计器上的类不重名就可以了.对于 dcef3 来说,修改 cefreg.pas
下的控件注册名就可以了,然后给几个空的新类即可,我的实现如下

//--------------------------------------------------
// 2019/4/27 11:53:35 //clq 为避免和 decf4 的同名控件冲突,只要在 cefvcl.pas 中加入这些代码即可
//(*
type
  TChromium_Newbt = class(TChromium);
  TChromiumDevTools_Newbt = class(TChromiumDevTools);
  TChromiumOSR_Newbt = class(TChromiumOSR);

{$ifdef DELPHI16_UP}
  TChromiumFMX_Newbt = class(TChromiumFMX);
{$endif}
//*)
//--------------------------------------------------

这就是 delphi 的强大之处,很多人都问我们这些还在用 delphi7 的人,质疑这是不是太落伍了,但我们一直喜欢着他. 对于我来说 d7 至少目前来说仍然有以下其他语言或者IDE工具无法替代的地方:
1.不需要 vc 那样附带另外的运行库包;
2.不需要庞大的运行库,例如 java python .net;
3.它是编译型的本地语言,不是脚本,不过我现在也越来越喜欢脚本语言;
4.d7的 ide 对比现在的开发环境,极度的小巧,占用内存极低,就像 vc6.不过这对于我来说不算但大的优点.
5.能在 xp/2003 下运行;
6.实在不行的时候,我们有开源版本可代替,它与 d7 代码的兼容性非常高.我个人觉得这一点是 d7 最大的优点! 这也是为什么 delphi 一直在进化却一直没代替 d7 的一个非常重要的原因;
7.还有一个对开其他人可能没想过,对于我来说却是至关重要的特性: delphi 的各个系统模块也就是各个系统的 pas dcu 文件都是可以自己重写代替的!至少到 d7 的时候还是这样,这就意味着我们能不停地
更新 delphi 的各个系统库本身,只要 d7 的原始编译器能用它就永远不会过时,因为d7对 dll 的调用是非常独立的,和语言本身并没有什么关系,只要 windows 系统 dl 调用的方式不变,我们就可以一直升级
它的系统调用能力,而这种 dll 调用方式在很长的未来里都不太可能改变.
举一个例子:d7 的拖放功能在 win10 下时是有很严重的闪烁的,我就是通过修改了基础常用控件 pas 就避免了这个问题.而且因为 delphi 对 pas 单元引用的灵活性,我只要把修改后的文件放在项目源码目录
中即可都不用修改 d7 本身的任何源码就可以实现了. 通过这种方式我甚至修改过 d7 的内存分配实现! 改天我放个示例到 github 上.
8.再有就是 delphi 的作者赋予 delphi 本身的强大的 rad 功能,说实在的现在的什么 xcode ,android studio 的界面设计器与 delphi 比都是笑话,唯一能和 delphi 比的就是 vb 和那个同样是 delphi 作者出品
的宇宙第一 ide - C# 在 vs 中的设计器. 说实在的 css 的布局方式升级了那么多次,仍然如此难用也实在是够蠢的. 还有什么 gtk qt 的设计器更是愚不可及.



clq
2019-08-10 19:43:05 发表 编辑

问题4:为何直接设置 html 源码时不工作?

原始的代码是实现比较简单的,使用接口 web1.Browser.MainFrame.LoadString 函数即可。
但初学者其实在都不会一次成功,原因有以下几个:
1.浏览器控件需要有一个初始化过程,一般的处理方法是先在窗口 oncreate 时访问一下 'about:blank' 或者是其他地址,目的是让浏览器生成相应框架。
2.不能在初始化后就立即执行这个过程,而是在按钮等事件中。有网友推荐在控件自己的 Chromium.Create 事件中再调用。
3.在使用 delphi7 的版本时,调用 web1.Browser.MainFrame 时就可能为空,这时候要用 web1.Browser.GetMainFrame 来代替。这是一个很奇怪的问题,我查看了代码按道理二者应该是一样的,
不过目前来说就是要如此处理。另外我测试了 dcef3 的官方源码其实是可以的,而自己向一个空白工程中拖放控件就是不行的,估计是某个初始化函数没有调用吧。简单应用时就这样处理就好了。
4.LoadString 函数的第二个参数一定要填充,并且得是一个看上去真实的网址,原因也不明,估计是 cef 源码中对网址进行了校验。如果不知道怎么放,可以直接用 'about:blank' 填充第二个参数。

可参考
https://www.cnblogs.com/xtfnpgy/p/9285377.html
https://stackoverflow.com/questions/17640526/delphi-tchromium-load-from-variable-function-not-working#
我复制了一份在
http://newbt.net/ms/vdisk/show_bbs.php?id=5880CD539CDA26CD59013F19B0ACB358&pid=160

我自己的一个测试代码如下,环境就是 delphi7 下的 dcef3,新建立的工程,没有执行任何初始化函数,直接在窗体上放的控件。

procedure TfrmMain.btnSendClick(Sender: TObject);
var
  frm: ICefFrame;
begin
  inherited;

  //直接设置 cef 控件的源代码

  //frm := web1.Browser.MainFrame;
  frm := web1.Browser.GetMainFrame; //据说 delphi7 下用 MainFrame 有问题的时候要用 GetMainFrame 代替

//  web1.Browser.MainFrame.LoadUrl('local://c/');

//  frm.LoadUrl('local://c/'); //奇怪,不行

//  web1.Browser.MainFrame.LoadUrl(web1.DefaultUrl);

  //frm.LoadString('20000000000000000000002',''); //不行
 
  //参考 https://stackoverflow.com/questions/17640526/delphi-tchromium-load-from-variable-function-not-working
  frm.LoadString('20000000000000000000002','http://123.com'); //ok,一定要有一个地址
  //frm.LoadString('20000000000000000000002','about:blank'); //ok
//
//  web1.Browser.MainFrame.LoadString('aaa', '');
end;




clq
2019-08-13 15:02:17 发表 编辑

[图片]
附注2:dcef3的2623版本出错时,重新 build 能解决大部分的问题

在目前常用的 dcef3 中出错的话最好重新 build 一下,我发现前面问题4中的也可以通过这个方法来处理。
最常见的时会使取得的接口无法正常操作,或者干脆为 nil ,例如以下代码所示

procedure TfrmMain.webAddrBeforeBrowse(Sender: TObject;
  const browser: ICefBrowser; const frame: ICefFrame;
  const request: ICefRequest; isRedirect: Boolean; out Result: Boolean);
begin

  //这时候的 request 接口已经取错了,但 delphi7 并不自己报错,表现为这个 showmessage 并不弹出窗口,同时会在其他操作时提示 AS 访问错误。
  ShowMessage(request.Url);


另外,原始示例代码中就有一个避免异常的代码,要写在 CloseQuery 事件中,如下。
procedure TMainForm.FormCloseQuery(Sender: TObject; var CanClose: Boolean);
begin
  // avoid AV when closing application
  if CefSingleProcess then
    crm.Load('about:blank');
  CanClose := True;
end;



clq
2019-08-13 15:09:19 发表 编辑


问题5:如何让浏览器访问某个地址不跳转?

这在需要将浏览器的某些动作让程序来完成时很有用,这样相当于将参数从浏览器传递给了程序。解决方法是在 BeforeBrowse 事件中取得地址,然后返回一个 true 值就可以了。
以下是 delphi 代码,其他语言其实类似。


procedure TfrmMain.webAddrBeforeBrowse(Sender: TObject;
  const browser: ICefBrowser; const frame: ICefFrame;
  const request: ICefRequest; isRedirect: Boolean; out Result: Boolean);
begin

  ShowMessage(request.Url);

  Result := false;

end;

基本上就是 附注2 的代码,同时也要参考一下  附注2 的解释,如果出错的话要怎样处理。



clq
2019-10-05 22:39:34 发表 编辑

附注3:delphi7下的独立窗口中有浏览器控件的话要在程序退出前释放。

否则会在程序退出时报 as 异常。处理起来其实也很简单,在主窗体或者接近主窗体的第二生成窗体中的 OnDestroy 事件中释放到新生成过的窗体就行了。

procedure TfrmMain.N_OpenChatClick(Sender: TObject);
//var
//  frmChat_v2:TfrmChat_v2;
begin
  frmChat_v2 := TfrmChat_v2.Create(nil); //这样会异常,不能自动释放 TChromium_Newbt
  //frmChat_v2 := TfrmChat_v2.Create(Application);

  frmChat_v2.Show;
  frmChat_v2.Chromium_Newbt1.Load('http://www.newbt.net');

//procedure TfrmMain.FormDestroy(Sender: TObject);
//begin
//
//
//  frmChat_v2.Free; //如果是独立创建的,要在主窗口释放前释放

end;


clq
2019-10-06 22:24:51 发表 编辑

附注4:delphi7下的独立窗口中有浏览器控件的话,嵌入另外一个 TForm 时异常要在窗体 show 之后再创建 cef 控件放到窗体上。

否则 cef 控件所在 TForm 在 show 或者 Visible := True 时提示 "AS 0000 " 错误,大致是取某个句柄时没取到造成的。
重新创建后的 cef 控件则是可以良好工作并且释放时无异常。

--------------------------------------------------
代码示例:

procedure TfrmMain.N21Click(Sender: TObject);
//var
//  frmChat_v3:TfrmChat_v3;
begin
  frmChat_v3 := TfrmChat_v3.Create(nil); //这样会异常,不能自动释放 TChromium_Newbt
  //frmChat_v3 := TfrmChat_v2.Create(Application);

  frmChat_v3.BorderStyle := bsNone;
  frmChat_v3.Parent := PanelM_1;

  frmChat_v3.web1.Free;

  //frmChat_v3.Show; //这个有异常//将 TChromium_Newbt 后创建即可
  frmChat_v3.Visible := True; //这个有异常//将 TChromium_Newbt 后创建即可

  frmChat_v3.web1 := TChromium_Newbt.Create(frmChat_v3);
  frmChat_v3.web1.Parent := frmChat_v3.PanelM_1;
  frmChat_v3.web1.Align := alClient;
  frmChat_v3.web1.Visible := True;
  frmChat_v3.web1.Load('http://www.newbt.net');

  frmChat_v3.web1.Align := alClient;  //这两句很重要,让 cef 控件显示出来
  frmChat_v3.web1.BringToFront;  //这两句很重要,让 cef 控件显示出来

  frmChat_v3.Align := alClient;


clq
2019-10-17 16:34:34 发表 编辑

附注5:dcef3 在 delphi7 中调用 GetMainFrame 仍然异常的处理。怎样判断 MainFrame 可以操作了?

我们在前面的问题中说过,某些时候 d7 中的 decf3 中,调用 web1.Browser.MainFrame 这样的代码是不行的,要换用 web1.Browser.GetMainFrame。
但是这种换用其实也有一个前提,那就是 cef 控件必须先正确的初始化,准确的时必须正确的初始化其中的 Document 所在 frame ,实际上就是要求先让控件含有 html 内容。
否则后续的操作是会有各种各样莫名其妙的异常的,这其实也好理解,比如 document.write() 这样的 js ,我 document 还没存在呢,执行能正确吗?

那么怎么正确的初始化 frame 呢?方法是 web.Load('http://www.newbt.net:8888'); 这样的访问一个网址。但通常情况下我们并不想具体显示一个页面,那么可以使用空白页面地址 web1.Load('about/blank');
注意,这是不能用 web1.Load(''); 来代替的。

在大多数情况下这样做工作得非常好,但是如果在创建 cef 控件后马上调用 js 接口,您就会发现仍然会异常!而且是不报异常,但实际上不能正常工作的那一种。并且还有一个更可怕的现象,
一旦某个 cef 控件出现这种异常了,会在后续创建的 cef 控件也产生 -- 即使后面创建的控件是正确初始化的!

原因其实也不难理解,就是 load 网址的时候还没完全加载就去调用 frame 了,所以出错,网友说可以在 OnLoadEnd 事件中得到完成的消息(https://oomake.com/question/1935008)。
但实际上对于 'about/blank' 这样的地址来说,并不会触发这个事件!!!

估计是因为 'about/blank' 这样的地址并不被 decf3 认为是真正的浏览事件吧。我们又尝试了多个类似事件都是不能,如果用定时器倒是可行的,不过怎样判断 frame 能用了呢?我们尝试了多个
属性后得出的结果是 web1.browser.FrameCount<1 就可以认为是没准备好,大于 0 了就是可以了。而传统上的
web1.Browser.GetMainFrame
web1.Browser.MainFrame
web1.Browser.HasDocument

全部不行,这是 dcef3 的情况,其他开发语言的网友也可以借鉴参考。有了这一属性判断,就可以在创建 cef 后加上以下这段代码,然后就可以直接立即操作 js 了。

--------------------------------------------------------
  i := 0;
  while web1.browser.FrameCount<1 do //可以
  begin
    Application.ProcessMessages;

    i := i+1;

    Sleep(100);

    if i>12 then
    begin
      showmessage_windows(0, 'cef 初始化超时');
      Exit; //有问题,超时了
    end; 
  end;

  ShowMessage(IntToStr(i));  //其实为 2,3 就出来了

  if web1.browser.FrameCount<1 then //ok//这个也不可靠,但正常情况下可以用,如果前面有 cef MainFrame 未初始化的情况下,也会出错
  begin
    ShowMessage('aaa2');
    Exit;
  end;

//  if not web1.Browser.HasDocument then //这个也不可靠
//  begin
//    ShowMessage('aaa');
//    Exit;
//  end;

  //这时 MainFrame 不一定建立了//这种判断没用
//  if web1.Browser.MainFrame = nil then //如果您仍想使用MainFrame,请在OnLoadEnd事件中执行此操作。
//  begin
//    //ShowMessage('aaa');
//    Exit;
//  end;


 
  //ShowMessage(BoolToStr(web1.Browser.MainFrame.IsValid)); //没用

  frm := web1.Browser.GetMainFrame;

  msg := get_value(s, '<body>', '</body>');
  //frm.LoadString('消息:' + msg,'http://123.com'); //ok,一定要有一个地址

  //原始消息可能是 '<iq type="result" id="3" from="127.0.0.1"></iq><message to='t2@127.0.0.1' from="t1@127.0.0.1/Spark" type='chat'><body>00</body><x xmlns="jabber:x:event"><offline/><composing/></x><active xmlns='http://jabber.org/protocol/chatstates'/></message>'
  //所以要先取出 message 这个节点的内容才行
  s := get_value(s, 'message', ''); //先去掉 message 前面的内容
  //gLastFromUser := get_value(xmpp_format(s), 'from=', ' ');
  fromUser := get_value(xmpp_format(s), 'from="', '"');

  //web1.Browser.
  //frm.ExecuteJavaScript();
  js := 'document.write("aaa中文")';
  js := 'document.write("aaa中文' + msg + '")';

  //注意,这里执行的是 unicode 的字符串内容,而收到的消息体是 utf8 的,要转换
  //UTF8Decode()
  ////js := 'document.write("aaa中文' + UTF8Decode(msg) + '")'; //ok
  js := 'document.write("消息:' + UTF8Decode(msg) + '<br />")';
  frm.ExecuteJavaScript(js, 'about:blank', 0);


  //gRecvBuf := ''; //简单清空其实也是可以的

--------------------------------------------------------
2019.10.25 第一次更正

测试下来还是 OnLoadEnd 比较合适,FrameCount 属性在获取时还是有时候会破坏 cef 的一些东西。原因不明,就当是 decf3 的 bug 吧。




clq
2019-12-04 13:34:50 发表 编辑

附注6:关于 附注4 的修正

近日在特殊情况下测试时发现附注4的方法有问题,表现为在要被嵌入的窗体(下称主窗体)中没有 cef 控件,或者 cef 控件没有正常访问过一个网络上的地址时,新嵌入的窗体中的 cef 控件也会失效。
同时这个主窗体中的 cef 还不能是访问本地 html 的;还不能是访问一个不存在的网页的;估计还有很多情况下也不行。这是 win10 下的情况,在 xp 下更为严重。

解决方法倒也简单(这可是我加班到晚上2点都没解决的,看到的网友必须得感谢我 :) ),就是在窗体上将 cef 变量移动到 public 处,并在设计器中删除这个控件。
估计原因是 dcef3 的消息机制处理得有问题,没有考虑、测试过这种情况。

另外,如果使用

CefSingleProcess := False;

可以解决这个问题,但是开这个的话异常太多了



总数:14 页次:1/2 首页 下一页  >>  尾页  
总数:14 页次:1/2 首页 下一页  >>  尾页  


所在合集/目录
浏览器内核 更多



发表评论:
文本/html模式切换 插入图片 文本/html模式切换


附件:



NEWBT官方QQ群1: 276678893
可求档连环画,漫画;询问文本处理大师等软件使用技巧;求档softhub软件下载及使用技巧.
但不可"开车",严禁国家敏感话题,不可求档涉及版权的文档软件.
验证问题说明申请入群原因即可.

Copyright © 2005-2020 clq, All Rights Reserved
版权所有
桂ICP备15002303号-1