---
# System prepended metadata

title: Immersive Mode Handling
tags: [Chromium-Graphics, elkurin-daily-note]

---

# Immersive Mode Handling

Immersive mode is a feature in Chromebook to enter fullscreen but still can access to the toolbar on top of the window by swiping down.  
It's similar to fullscreen, but the slightly different feature.  
Let's see how the immersive mode is handled in chromeos.

## Overview
Immersive mode is currently available on ChromeOS and macOS.  
In this note, we follow the stack used in ChromeOS.

[ImmersiveModeController](https://source.chromium.org/chromium/chromium/src/+/main:chrome/browser/ui/views/frame/immersive_mode_controller.h;l=39;drc=f5bdc89c7395ed24f1b8d196a3bdd6232d5bf771) controls immersive mode.  
Most of the methods of this class is declared virtually, and each impelmentation is done by platform-specific class which is, for ChromeOS, [ImmersiveModeControllerChromeos](https://source.chromium.org/chromium/chromium/src/+/main:chrome/browser/ui/views/frame/immersive_mode_controller_chromeos.h;l=21;drc=c5e3187e0a3dd3ffd1174eb68b0a85d8eb4ca0ef).

ImmersiveModeControllerChromeos is also a bridge class.  
The primal implementation styas in [ImmersiveFullscreenController](https://source.chromium.org/chromium/chromium/src/+/main:chromeos/ui/frame/immersive/immersive_fullscreen_controller.h;l=55;drc=89e1f63c97ae8c66ff1b8de4fbf564978349ac70) which is inside chromeos/* repo.  
ImmersiveModeControllerChromeos class holds [`controller_`](https://source.chromium.org/chromium/chromium/src/+/main:chrome/browser/ui/views/frame/immersive_mode_controller_chromeos.h;l=75;drc=c5e3187e0a3dd3ffd1174eb68b0a85d8eb4ca0ef) object which is ImmersiveFullscreenController.

## How to toggle immersive mode
There are 2 passes to enable immersive mode.  

### From FullscreenController
One is from [FullscreenController](https://source.chromium.org/chromium/chromium/src/+/main:chrome/browser/ui/exclusive_access/fullscreen_controller.cc;l=527-529;drc=f5bdc89c7395ed24f1b8d196a3bdd6232d5bf771).  
When FullscreenController receives the fullscreen state transition, it calls [ExclusiveAccessContext::EnterFullscreen](https://source.chromium.org/chromium/chromium/src/+/main:chrome/browser/ui/exclusive_access/exclusive_access_context.h;l=44;drc=f5bdc89c7395ed24f1b8d196a3bdd6232d5bf771) which is, for ChromeOS normal window, overriden by [BrowserView::EnterFullscreen](https://source.chromium.org/chromium/chromium/src/+/main:chrome/browser/ui/views/frame/immersive_mode_controller_chromeos.h;l=40;drc=c5e3187e0a3dd3ffd1174eb68b0a85d8eb4ca0ef).  

Then it calls [ImmersiveModeControllerChromeos::SetEnabled](https://source.chromium.org/chromium/chromium/src/+/main:chrome/browser/ui/views/frame/immersive_mode_controller_chromeos.h;l=40;drc=c5e3187e0a3dd3ffd1174eb68b0a85d8eb4ca0ef) to set true to toggle fullscreen.  
There are some cases where it does not go to immersive mode.  
For example, LockedFullscreen is one of the exceptions. LockedFullscreen is a fullscreen mode where users are not allowed to go out without going through private apis. The main purpose of this feature is education. Teachers create tests and let students open it, and disallow going out of that window until students close it so that students won't cheet.

SetEnabled passes the state change to chromeos repo's ImmersiveFullscreenController by calling [ImmersiveFullscreenController::EnbleForWidget](https://source.chromium.org/chromium/chromium/src/+/main:chromeos/ui/frame/immersive/immersive_fullscreen_controller.cc;l=287;drc=f5bdc89c7395ed24f1b8d196a3bdd6232d5bf771).  
This sets property named `kImmersiveIsActive`.  

Now this property change triggers [OnWIndowPropertyChanged](https://source.chromium.org/chromium/chromium/src/+/main:ui/aura/window_observer.h;l=72?q=OnWindowPropertyChanged&ss=chromium%2Fchromium%2Fsrc) observer and its implementation in [ImmersiveFullscreenController::OnWindowPropertyChanged](https://source.chromium.org/chromium/chromium/src/+/main:chromeos/ui/frame/immersive/immersive_fullscreen_controller.cc;l=211;drc=f5bdc89c7395ed24f1b8d196a3bdd6232d5bf771) receives it to [UpdateEnabled](https://source.chromium.org/chromium/chromium/src/+/main:chromeos/ui/frame/immersive/immersive_fullscreen_controller.cc;l=731;drc=f5bdc89c7395ed24f1b8d196a3bdd6232d5bf771).  
Here, we can see such as OnImmersiveFullscreenEntered, OnImmersiveFullscreenExited...

### From BrowserNonClientFrameViewChromeos
[BrowserNonClientFrameViewChromeOS::OnTabletModeToggled](https://source.chromium.org/chromium/chromium/src/+/main:chrome/browser/ui/views/frame/browser_non_client_frame_view_chromeos.cc;l=634;drc=f5bdc89c7395ed24f1b8d196a3bdd6232d5bf771) is another location to call SetEnabled.

When the device turns into tablet mode, we must go out of immersive mode since both UI conflicts to each other.  
For example, on swiping down the top of the screen has a different UI. On tablet mode, it should show the list of tabs in the window. On immersive mode, it should show the toolbar.  
On the other hand, swiping up the bottom of the screen also has a different behavior. On tablet mode, it should go into Overview mode where we can observe all windows at glance with a big swip, but immersivde mode doesn't.  
Because of this conflict, we exit immersive mode when tablet mode is toggled.

## Reveal
Reveal is another crutial part in immersive mode.  
As mentioned already, when swiping down the top of the screen, we can observe the toolbar.  
This is called "Revealed".

Since the toolbar may be revealed by user's action and it should look smooth, the toolbar UI is drawn from the beginning instead of drawring when swiped.  

[BrowserNonClientFrameViewChromeOS::GetTopInset](https://source.chromium.org/chromium/chromium/src/+/main:chrome/browser/ui/views/frame/browser_non_client_frame_view_chromeos.cc;l=235;drc=f5bdc89c7395ed24f1b8d196a3bdd6232d5bf771) returns the diff between the screen and actual window bounds. It has the following impl.
```cpp=
if (!GetShouldPaint() {
  const ImmersiveModeController* const immersive_controller =
    browser_view()->immersive_mode_controller();
  if (immersive_controller->IsEnbled() &&
      !immersive_controller->IsRevealed()) {
    return (-1) * browser_view()->GetTabStripHeight();
  }
  ...;
}
```
As you can see, when it is not revealed, the top inset is calculated as `-height_of_toolbar`.  
Toolbar is located just above the screen area.