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.