Display Cutout and Safe Area Insets
This document outlines the architecture and data flow for handling display cutouts (i.e., "notches") and their associated safe area insets in Chromium. This mechanism allows web content to utilize the entire screen on devices with cutouts, using web standards like the viewport-fit meta tag and the env() CSS function.
Relevant classes:
- //content/browser/display_cutout/
- //components/browser_ui/display_cutout/
- //chrome/android/java/src/org/chromium/chrome/browser/display_cutout/
- //ui/android/java/src/org/chromium/ui/insets/
Core Concepts
- Display Cutout: A physical area on a display that is not available for rendering, typically housing cameras and sensors.
- Safe Area Insets: The area of the screen that can be obstructed by device cutouts, status bars, or other system UI. The insets are provided as a set of distances from the edges of the viewport (top, left, bottom, right). Content can use this this inset to position themselves to ensure they are not blocked by system UI. Read more
viewport-fit: A CSS descriptor, specified via a<meta>tag, that controls how a web page is displayed relative to the safe area. Read moreauto(default): The web page is confined to the safe area.contain: The web page is scaled to fit entirely within the safe area.cover: The web page is scaled to fill the entire viewport, including the area "under" the cutout. This is used to create an "edge-to-edge" experience.- For example, when the browser goes into fullscreen mode, pages with
viewport-fit=coverwill draw under the display cutout, while pages with other values will not.
- For example, when the browser goes into fullscreen mode, pages with
Useful resources:
- https://developer.android.com/develop/ui/views/layout/display-cutout#:~:text=Enable Developer options.,Select the cutout type.
- https://developer.chrome.com/docs/css-ui/edge-to-edge#safe-area-insets
Architecture Overview
The implementation spans three main layers of the Chromium stack: Blink, C++ browser, Java UI. The entire process involves a round trip of information. The sequence diagrams illustrating this data flow can be found below.

Source see Data flow source.
Blink (Renderer Process)
Parses the viewport-fit meta tag and uses the safe area insets provided by the browser to resolve the env() CSS function.
C++ Browser Layer (//content/browser/display_cutout)
Acts as the central hub for display cutout information.
- From Blink to Java, it tracks the viewport-fit state for each web page and notifies the UI layer to adjust the Android window layout.
- From Java to Blink, it funnels the Android window inset data back to the renderer.
Java UI Layer (//chrome/android and //components/browser_ui)
Interacts with the Android OS to get cutout information, sets the appropriate window flags to enable drawing under the cutout or into system bars, and provides the safe area inset values to the C++ layer.
Viewport-fit flow
Render (Blink)
- Parses the
<meta name="viewport" content="viewport-fit=...">tag. - Notifies the browser process of the value via the
DisplayCutoutHostMojo interface.
C++
[2] SafeAreaInsetsHostImpl (//content/browser/display_cutout)
- It is the Mojo endpoint for
blink::mojom::DisplayCutoutHost, receivingNotifyViewportFitChangedcalls from Blink. - It uses
DocumentUserDatato correctly associate aviewport-fitvalue with each document. - It determines the single "active" frame that should control the cutout settings (the fullscreen frame takes precedence over the primary main frame).
- When the active frame's viewport-fit value is updated, it calls
WebContentsImpl::NotifyViewportFitChanged()to notify observers of the change.
Java UI
- Receives the signal from
WebContentObserver.viewportFitChanged()and forwards the info toDisplayCutoutTabHelper.
[4] DisplayCutoutTabHelper (//chrome/android/java/src/org/chromium/chrome/browser/display_cutout)
- It is a
UserDatascoped to aTab, and creates and owns theDisplayCutoutController. - Acts as glue code by implementing the
DisplayCutoutController.Delegateto provide the controller with access to tab-specific objects likeWebContentsandWindowAndroid. - The viewport-fit data is forwarded to the
DisplayCutoutController.
[5] DisplayCutoutController (//components/browser_ui/display_cutout)
- Calculates the appropriate LayoutParams.layoutInDisplayCutoutMode window flag based on the page's viewport-fit value and other states (e.g., fullscreen).
- viewport-fit=cover → LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES
- viewport-fit=contain → LAYOUT_IN_DISPLAY_CUTOUT_MODE_NEVER
- Applies this flag to the Activity window, telling Android to let the app draw behind the display cutout.
Safe area inset flow
Java UI
[1] InsetObserver (//ui/android/java/src/org/chromium/ui/insets/)
- Observe the Android window insets and coordinate the inset consumption.
- Translate the display cutout from window insets into safe area insets values based on whether the Activity is drawing edge to edge.
- The safe-area-inset is dispatched from
WindowInsetObserver.onSafeAreaChanged()
[2] DisplayCutoutController (//components/browser_ui/display_cutout)
- Implements
WindowInsetObserver.onSafeAreaChanged()to receive safe area updates calculated based on the Android Window Insets. - When it receives new insets from the
InsetObserver, it scales the pixels into dips and pushes them to the C++ layer via a JNI call (WebContents.setDisplayCutoutSafeArea()).
C++ Browser Layer (//content/browser/display_cutout)
- When
SetDisplayCutoutSafeArea()is called from the Java layer, it sends the inset values to the active frame in Blink via theblink::mojom::DisplayCutoutClientinterface. - This class is also responsible for picking the correct "Active" frame that receives the
safe-area-inset-*. At all times, only one frame will receive the safe-area-inset.- If the browser is not in fullscreen mode, the main frame of the current page will be seen as "active" frame.
- If the browser is in fullscreen mode, the frame that requested fullscreen will be seen as "active" frame. This is even the case when a subframe requested fullscreen mode.
- When active frame switches, the
safe-area-inset-*values in the previous active frame will be reset to 0.
Blink (Renderer)
[4] blink::DisplayCutoutClientImpl (//third_party/blink/renderer/core/frame)
- Receives safe area insets as
gfx::Insetsfrom the browser process via theDisplayCutoutClientinterface. - It passes the
gfx::Insetsto the currentblink::Page::SetMaxSafeAreaInsets.
[5] blink::Page (//third_party/blink/renderer/core/page/)
- The Page receives the
gfx::Insetsand stores them asscaled_max_safe_area_insets_. - If the
DynamicSafeAreaInsetsEnabledsetting is active, the final applied safe area can be adjusted dynamically. The functionUpdateSafeAreaInsetWithBrowserControlscalculates the effective bottom inset by considering the height and visibility of the browser's bottom controls (like a navigation bar). As the browser controls slide into view, the bottom safe area inset is reduced, and as they slide out, the inset is increased. - Once the final safe area insets are calculated, the
SetSafeAreaEnvVariablesfunction is called to update the CSSenv()variables. This updatessafe-area-inset-*andsafe-area-max-inset-*.- See Dynamic safe area inset for more details.
End-to-End Example: viewport-fit: cover
- A web page loads with
<meta name="viewport" content="viewport-fit=cover">. - Blink parses the tag and sends
coverto the browser process via aDisplayCutoutHostMojo call. SafeAreaInsetsHostImpl(C++) receives the value. It storesviewport-fit=coverfor the document and notifiesWebContentsImplof the change.WebContentsImplpropagates this notification to its Java observers.DisplayCutoutTabHelper(Java) receives thecovervalue and passes it to theDisplayCutoutController.DisplayCutoutController(Java), seeingcoverand assuming the tab is fullscreen, sets the Activity window'slayoutInDisplayCutoutModetoLAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES.- The Android OS responds to the window flag change by sending new safe area insets (e.g., a 48px top inset) to the app's
InsetObserver. DisplayCutoutController'sonSafeAreaChangedmethod is triggered. It callsWebContents.setDisplayCutoutSafeArea()with the new insets.- This JNI call reaches
SafeAreaInsetsHostImpl(C++), which forwards the insets to the activeRenderFrameHostin Blink. - Blink receives the insets. Any CSS on the page, such as
padding-top: env(safe-area-inset-top);, is now correctly resolved, and theenv()function returns48px. The page can now adjust its layout to avoid the display cutout.
Dynamic Safe Area Inset (Chrome status)
In certain scenarios, such as when Chrome renders edge-to-edge content outside of fullscreen mode, the safe area insets must be adjusted dynamically. This is necessary to account for the presence of browser controls (e.g., the top and bottom toolbars), which can overlay the web content.
As a user scrolls the page, the browser controls retract to offer a more immersive experience. This retraction changes the size of the viewport and, crucially, the safe area available to the web content. The safe-area-inset-* CSS environment variables are updated dynamically to reflect these changes.
For example, as the bottom browser toolbar slides into view, the safe-area-inset-bottom value decreases, ensuring that web content repositions itself to remain accessible. Conversely, as the toolbar slides out of view, the inset increases.
To provide web developers with a stable value for layout calculations, safe-area-max-inset-* has been introduced. This variable represents the maximum possible inset value, which is the state when the browser controls are fully retracted. This allows developers to design layouts against a consistent and predictable safe area boundary.
References
- For
safe-area-max-inset-*, see the CSSWG discussion for more details. - For more on browser controls, see
//docs/ui/android/browser_controls.md.
Appendix
Data flow source
@startuml
title Display Cutout Data Flow
box "Blink (Renderer)" #LightBlue
participant "ViewportData" as ViewportData
participant "DisplayCutoutClientImpl" as CutoutClient
participant "Page" as Page
end box
box "C++ Browser" #LightGreen
participant "SafeAreaInsetsHostImpl" as InsetsHost
end box
box "Java UI" #LightYellow
participant "TabWebContentsObserver" as TabObserver
participant "DisplayCutoutTabHelper" as TabHelper
participant "DisplayCutoutController" as CutoutController
participant "InsetObserver" as InsetObserver
end box
participant "Android Window" as AndroidOS
== Flow 1: viewport-fit value (Blink to Java) ==
autonumber "<b>[1]"
activate ViewportData
ViewportData -> InsetsHost: NotifyViewportFitChanged()
deactivate ViewportData
activate InsetsHost
InsetsHost -> TabObserver: WebContentsObserver.viewportFitChanged()
deactivate InsetsHost
activate TabObserver
TabObserver -> TabHelper: forwards viewport-fit
deactivate TabObserver
activate TabHelper
TabHelper -> CutoutController: forwards viewport-fit
deactivate TabHelper
activate CutoutController
CutoutController -> AndroidOS: Set Window LayoutParams
deactivate CutoutController
== Flow 2: safe-area-inset value (Java to Blink) ==
autonumber "<b>[2]"
activate AndroidOS
AndroidOS -> InsetObserver: Window Insets Changed
deactivate AndroidOS
activate InsetObserver
InsetObserver -> CutoutController: onSafeAreaChanged()
deactivate InsetObserver
activate CutoutController
CutoutController -> InsetsHost: setDisplayCutoutSafeArea() [JNI]
deactivate CutoutController
activate InsetsHost
InsetsHost -> CutoutClient: SetSafeArea()
deactivate InsetsHost
activate CutoutClient
CutoutClient -> Page: SetMaxSafeAreaInsets()
note right of Page
Page receives insets and
updates CSS env() variables.
end note
deactivate CutoutClient
@enduml