CEF3开发者系列之与Flash插件纠缠不清的那些问题
概述
Flash Player也曾兴盛一时,随着国外各大厂商对Flash插件围攻,短短几年间,从不支持到完全抛弃,Flash Player慢慢从互联网上消失。一方面归咎于Flash自身的不安全、低效率,还有保守封闭,另外一方面HTML5的兴起,也加速了Flash的灭亡。说到底是Flash自己作的孽,但造成的后果有时候需要开发者来背。国内还有很多PC互联网时代留下的产品,比如众多的Flash游戏,还需要使用Flash Player来延续他们的生命和服务。就像一对夫妻不想一起过下去了,却被孩子拴住了,不得不必须在一起将就缠绵了。
在使用CEF3作为框架的过程中,随着Chromium内核的升级,使用的CEF3也要随之升级。虽然谷歌抛弃了Flash,但我们在使用CEF3框架时,还是需要比较好的支持Flash插件,尽量给用户一个好的体验。最近升级到CEF3-80.1.15,使用的是chromium-80.0.3987.163。支持Flash插件加载时,遇到以下一些问题,特总结出来供参考。
1、 加载旧版本Flash Player插件时,不会主动加载出来,需要点击鼠标右键,手动运行插件。
2、 加载Flash插件时,会弹出黑色的CMD命令行窗口。
3、 在Flash内容中的编辑框,无法获取鼠标焦点,且无法切换输入法,即无法输入中文。
以下都是基于CEF3-80.1.15,chromium-80.0.3987.163,ppapi类型的Flash插件的解决方案。
一、 如何解决旧版本Flash插件在CEF3中主动加载。
加载过期Flash插件时,会出现提示:Adobe Flash Player is out of date,必须用户点击,进行主动加载。
在client_browser.cc中的OnBeforeCommandLineProcessing方法里,加上这么一段,通过命令行的方式,允许加载自定义路径下的过期插件
command_line->AppendSwitch("--allow-outdated-plugins"); //允许过期插件加载 command_line->AppendSwitchWithValue("ppapi-flash-path", flashPluginPath); //Flash插件路径 command_line->AppendSwitchWithValue("ppapi-flash-version", "29.0.0.171"); //Flash插件版本 command_line->AppendSwitchWithValue("plugin-policy", "allow"); //允许Flash加载
如果是加载系统中已经安装的Flash,直接设置下面命令行
command_line->AppendSwitch("--disable-web-security");//关闭同源策略 command_line->AppendSwitch("--enable-system-flash"); //使用系统flash command_line->AppendSwitch("--load-extension"); command_line->AppendSwitch("--allow-outdated-plugins");
但仅仅这样还不够,因为在比较新的chromium内核中,确切说在Chromium76之后,对Flash进行了特殊关照,还需要进一步的设置。在创建Browser进程前,即调用CreateBrowser前:
CefBrowserHost::CreateBrowser(window_info, client_handler_, client_handler_->startup_url(), settings, extra_info, request_context);
还需要加上以下加上以下代码,才能允许对旧版本插件进行主动加载
CefString error; CefRefPtrvalue = CefValue::Create(); value->SetInt(1); CefRefPtr valueBool = CefValue::Create(); valueBool->SetBool(TRUE); request_context->SetPreference("plugins.allow_outdated", valueBool, error); request_context->SetPreference("plugins.run_all_flash_in_allow_mode", valueBool, error); request_context->SetPreference("profile.default_content_setting_values.plugins", value, error);
如果做了这些事情,还是不能生效。去看看初始化的时候,CEF3中选用的消息机制是什么。
例如使用WTL或者MFC框架,自己本身有一套消息处理机制,那么设置settings.multi_threaded_message_loop = true 同时message_loop.reset(new MainMessageLoopStd);否则使用按照CEF3的例子cefclient中进行设置。
二、 如何解决Flash加载启动前,弹出黑色CMD框
cef3加载flash,会出现弹出命令行窗口,显示not sandboxed,影响使用体验。该问题有以下两个方案解决。
1、 通过Hook的方式,直接拦截黑色弹窗弹出。可以使用easyhook这个第三方库进行hook操作,简单又方便,不用开发者再去写繁杂的hook过程,在https://easyhook.github.io/downloads.html下载 easyhook。具体使用方法见文档和示例。
主要步骤:由于打开cmd属于启动新的进程,所以hook CreateProcessA 和CreateProcessW,获取进程启动时的命令行,如果命令行中带有“echo NOT SANDBOXED”,则进行拦截
关键代码如下:
typedef BOOL(WINAPI *realCreateProcessAPtr)(LPCSTR lpApplicationName, LPSTR lpCommandLine, LPSECURITY_ATTRIBUTES lpProcessAttributes, LPSECURITY_ATTRIBUTES lpThreadAttributes, BOOL bInheritHandles, DWORD dwCreationFlags, LPVOID lpEnvironment, LPCSTR lpCurrentDirectory, \ LPSTARTUPINFOA lpStartupInfo, \ LPPROCESS_INFORMATION lpProcessInformation); realCreateProcessAPtr prealCreateProcessA; void DoHook() { HMODULE hKernel32 = LoadLibrary(L"kernel32.dll"); if (!(prealCreateProcessA = (realCreateProcessAPtr)GetProcAddress(hKernel32, "CreateProcessA"))) { return; } if (!(prealCreateProcessW = (realCreateProcessWPtr)GetProcAddress(hKernel32, "CreateProcessW"))) { return; } NTSTATUS resultA = LhInstallHook(prealCreateProcessA, MYCreateProcessA, NULL, &hAHookTrackInfo); NTSTATUS resultW = LhInstallHook(prealCreateProcessW, MYCreateProcessW, NULL, &hWHookTrackInfo); ………………………… } BOOL WINAPI MYCreateProcessA( LPCSTR lpApplicationName, LPSTR lpCommandLine, LPSECURITY_ATTRIBUTES lpProcessAttributes, LPSECURITY_ATTRIBUTES lpThreadAttributes, BOOL bInheritHandles, DWORD dwCreationFlags, LPVOID lpEnvironment, LPCSTR lpCurrentDirectory, LPSTARTUPINFOA lpStartupInfo, LPPROCESS_INFORMATION lpProcessInformation ) { std::string strCommandLine = lpCommandLine; if (string::npos != strCommandLine.find("echo NOT SANDBOXED")) { return TRUE; } else { return (prealCreateProcessA)(lpApplicationName, lpCommandLine, lpProcessAttributes, lpThreadAttributes, bInheritHandles, dwCreationFlags, lpEnvironment, lpCurrentDirectory, lpStartupInfo, lpProcessInformation); } }
以上只是关键代码,具体代码根据需要进行不全,MYCreateProcessW按照 MYCreateProcessA补全就行
2、用二进制编辑软件,比如winhex,对flash player的dll文件进行反编译。搜索comspec修改为somspec,(修改的名字只要和comspec不相同即可)修改cmd.exe为cm1.exe (修改的名字只要和cmd.exe不相同即可),然后保存回编译。
三、 如何解决在Flash中无法输入中文的问题
谷歌在Chrome 63中于2017年底推出了Site Isolation,使其成为企业IT员工的一个选择,他们可以自定义防御工作,以保护工作人员免受外部网站上的威胁。后来,在推出的Chromium 66中,谷歌向普通用户开放了现场测试,一般用户可以通过chrome://flags选项启用站点隔离。谷歌明确表示,网站隔离最终将成为浏览器的默认设置,但该公司首先想要验证修复程序,以解决早期测试中出现的问题。用户可以通过更改选项页面中的一个设置来拒绝参与试用。打开谷歌浏览器 输入chrome://flags/#site-isolation-trial-opt-out 将该项设置为 disabled ,重启即可。
所以在CEF3 66之后版本,也是默认设置了网站隔离,导致在Flash里的编辑框中,无法获取鼠标焦点,同时无法切换输入法,导致无法输入中文,只能输入英文。既然搞清楚问题了,解决问题就好办了。我们在CEF3中做类似的设置就好。Chromium命令行中有--disable-site-isolation-trials 命令,我们在命令行初始化时,加入即可。在client_browser.cc中的OnBeforeCommandLineProcessing方法里,加上如下代码:
command_line->AppendSwitch("--disable-site-isolation-trials");
总结
CEF3是个好框架,但Flash却不是个好插件,好男碰到了渣女,不得不一起过日子,必然会造成各种问题。这些问题其实就是一层窗户纸,解决的时候查了数不清的资料,掉了一把又一把的头发,但解决方案往往只有几行代码。作为一个开发工程师,使用框架或者第三方引擎的时候,不要满足于浅层的调用,还要去深入学习,才能比较全面又清晰的理解这些框架或引擎。解决问题不至于无从下手,随着知识和经验的积累,从而做到举重若轻。