Please be notified that this article is meant to disable the HighDPI API call for CEF, not turn off the highDPI support function in CEF. This CEF itself is still fully capable of zooming as the display scaling.
Problem
Windows has its HighDPI display support and it consists of two parts:
- DPI awareness
- Virtual scaling
When an application that supports HighDPI rendering starts up, it should announce itself as DPI aware
.
There are different levels of awareness evolving along with different Windows version. For now, there are:
- DPI aware
- DPI aware Pre-monitor
- DPI aware Pre-monitor-V2
and of course DPI unaware...
All the different levels of awareness can be checked out HERE
Applications can either use manifest to statically declare their awareness or call APIs to declare BEFORE any of their windows get created.
CEF, by design, supports HighDPI naturally.
However, its way of supporting HighDPI is not controllable. It will declare itself as DPI awareness regardless whether your application that hosts CEF framework does or not, which causes big trouble. It will force your application to render in a DPI aware context making your application ui crap.
The culprit
In the source code
int BrowserMainRunnerImpl::Initialize(MainFunctionParams parameters) {
SCOPED_UMA_HISTOGRAM_LONG_TIMER(
"Startup.BrowserMainRunnerImplInitializeLongTime");
TRACE_EVENT0("startup", "BrowserMainRunnerImpl::Initialize");
// On Android we normally initialize the browser in a series of UI thread
// tasks. While this is happening a second request can come from the OS or
// another application to start the browser. If this happens then we must
// not run these parts of initialization twice.
if (!initialization_started_) {
initialization_started_ = true;
SkGraphics::Init();
if (parameters.command_line->HasSwitch(switches::kWaitForDebugger)) {
base::debug::WaitForDebugger(60, true);
}
if (parameters.command_line->HasSwitch(switches::kBrowserStartupDialog)) {
WaitForDebugger("Browser");
}
#if BUILDFLAG(IS_WIN)
base::win::EnableHighDPISupport();
// Ole must be initialized before starting message pump, so that TSF
// (Text Services Framework) module can interact with the message pump
// on Windows 8 Metro mode.
ole_initializer_ = std::make_unique<ui::ScopedOleInitializer>();
#endif // BUILDFLAG(IS_WIN)
gfx::InitializeFonts();
There is a function call base::win::EnableHighDPISupport();
which looks like this:
void EnableHighDPISupport() {
if (!IsUser32AndGdi32Available())
return;
// Enable per-monitor V2 if it is available (Win10 1703 or later).
if (EnablePerMonitorV2())
return;
// Fall back to per-monitor DPI for older versions of Win10.
PROCESS_DPI_AWARENESS process_dpi_awareness = PROCESS_PER_MONITOR_DPI_AWARE;
if (!::SetProcessDpiAwareness(process_dpi_awareness)) {
// For windows versions where SetProcessDpiAwareness fails, try its
// predecessor.
BOOL result = ::SetProcessDPIAware();
DCHECK(result) << "SetProcessDPIAware failed.";
}
}
As you can see from it, same as always, it has no switch. It always calls SetProcessDPIAware
whatsoever which in turn causes problems.
Locate the code
It is quite difficult to locate the accurate place of the code in the asm perspective.
if (!IsUser32AndGdi32Available())
return;
is clearly the ideal place we'd like to hook.
By looking into the function IsUser32AndGdi32Available
we can see:
bool IsUser32AndGdi32Available() {
static const bool is_user32_and_gdi32_available = [] {
// If win32k syscalls aren't disabled, then user32 and gdi32 are available.
PROCESS_MITIGATION_SYSTEM_CALL_DISABLE_POLICY policy = {};
if (::GetProcessMitigationPolicy(GetCurrentProcess(),
ProcessSystemCallDisablePolicy, &policy,
sizeof(policy))) {
return policy.DisallowWin32kSystemCalls == 0;
}
return true;
}();
return is_user32_and_gdi32_available;
}
it calls GetProcessMitigationPolicy
to do the check. This API call is not very common and is a great entrypoint to cut into.
Launching the ollydbg, but this time, instead of a simple bp GetProcessMitigationPolicy
, we need to manually open kernelbase.dll
, CTRL+N
and search for GetProcessMitigationPolicy
in it. Because OD will only set break on kernel32.dll which is simply a redirection and won't work in this case.
Now, run the process and wait for the break. It should gives you a "return to libcef.xxxxx" in the stack window.
Ctrl+F9
twice and F8
to return to the outside caller which is the function we're looking for.
With a few more steps, there is a long JMP another section of code, there you'll see a long JE
to the end of the function. This is the if we're looking for.
0D6230C6 803D A07D6614 0>CMP BYTE PTR DS:[0x14667DA0],0x0 ; jumped to
0D6230CD 0F84 DA000000 JE libcef.0D6231AD ; critical check, change to JMP
0D6230D3 A1 A47D6614 MOV EAX,DWORD PTR DS:[0x14667DA4]
0D6230D8 8B0D E0A66814 MOV ECX,DWORD PTR DS:[0x1468A6E0]
0D6230DE 64:8B15 2C00000>MOV EDX,DWORD PTR FS:[0x2C]
0D6230E5 8B0C8A MOV ECX,DWORD PTR DS:[EDX+ECX*4]
0D6230E8 3B81 3C010000 CMP EAX,DWORD PTR DS:[ECX+0x13C]
0D6230EE 0F8F 00010000 JG libcef.0D6231F4
0D6230F4 803D A07D6614 0>CMP BYTE PTR DS:[0x14667DA0],0x0
0D6230FB 74 39 JE SHORT libcef.0D623136
0D6230FD A1 CC7D6614 MOV EAX,DWORD PTR DS:[0x14667DCC]
0D623102 8B0D E0A66814 MOV ECX,DWORD PTR DS:[0x1468A6E0]
0D623108 64:8B15 2C00000>MOV EDX,DWORD PTR FS:[0x2C]
0D62310F 8B0C8A MOV ECX,DWORD PTR DS:[EDX+ECX*4]
0D623112 3B81 3C010000 CMP EAX,DWORD PTR DS:[ECX+0x13C]
0D623118 0F8F 42010000 JG libcef.0D623260
0D62311E 8B0D C87D6614 MOV ECX,DWORD PTR DS:[0x14667DC8]
0D623124 85C9 TEST ECX,ECX
0D623126 74 0E JE SHORT libcef.0D623136
0D623128 FF15 00A08514 CALL DWORD PTR DS:[0x1485A000] ; libcef.126745C0
0D62312E 6A FC PUSH -0x4
0D623130 FFD1 CALL ECX ; SetProcessDPIAwarenessInternal
0D623132 85C0 TEST EAX,EAX
0D623134 75 77 JNZ SHORT libcef.0D6231AD
0D623136 E8 95180000 CALL libcef.0D6249D0
0D62313B 31DB XOR EBX,EBX
0D62313D 83F8 07 CMP EAX,0x7
Change it to JMP
and save.
Result
Now we have disabled the CEF's force declaration of dpi awareness, we can freely decide if we would like to support HighDPI display or not by calling the SetProcessDpiAwareXXX
series API.
0 comment