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;
CefRefPtr value = 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 CreateProcessACreateProcessW,获取进程启动时的命令行,如果命令行中带有“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却不是个好插件,好男碰到了渣女,不得不一起过日子,必然会造成各种问题。这些问题其实就是一层窗户纸,解决的时候查了数不清的资料,掉了一把又一把的头发,但解决方案往往只有几行代码。作为一个开发工程师,使用框架或者第三方引擎的时候,不要满足于浅层的调用,还要去深入学习,才能比较全面又清晰的理解这些框架或引擎。解决问题不至于无从下手,随着知识和经验的积累,从而做到举重若轻。