<permission> Element (PEPC)This document provides a detailed technical overview of the <permission> element feature, also known as PEPC (Permission Element Policy Control). It is intended for developers who want to understand the implementation of this feature in Chromium.
The <permission> element is an HTML element that allows websites to embed a permission request UI directly into their content. The goal is to make permission requests more contextual, user-initiated, and trustworthy, leading to a better user experience and higher permission grant rates.
The core principle of the feature is a two-step permission request process:
<permission> element on the page.This two-step process is crucial for security, as it prevents websites from tricking users into granting permissions with a single click.
The following diagram illustrates the high-level architecture of the <permission> element feature:
+-------------------------------------------------------------------------------------------------+
| Renderer Process (Blink) |
| |
| +-------------------------+ |
| | HTMLPermissionElement | |
| +-------------------------+ |
| | |
| | 1. RegisterPageEmbeddedPermissionControl |
| | 3. RequestPageEmbeddedPermission |
| v |
| +-------------------------+ |
| | PermissionService | |
| +-------------------------+ |
| |
+-------------------------------------------------------------------------------------------------+
| |
| Mojo IPC |
v |
+-------------------------------------------------------------------------------------------------+
| Browser Process |
| |
| +-----------------------+ +--------------------------+ +-------------------------+ |
| | PermissionServiceImpl |----->| PermissionControllerImpl |----->| PermissionPromptFactory | |
| +-----------------------+ +--------------------------+ +-------------------------+ |
| | | |
| | 2. OnEmbeddedPermissionControlRegistered | 4. Create |
| v v |
| +---------------------------------+ +--------------------------+ |
| | EmbeddedPermissionControlClient | | EmbeddedPermissionPrompt | |
| +---------------------------------+ +--------------------------+ |
| | |
| 5. Create & Show | |
| v |
| +----------------------------------+ |
| | EmbeddedPermissionPromptBaseView | |
| +----------------------------------+ |
| |
+-------------------------------------------------------------------------------------------------+
The implementation of the <permission> element spans across the renderer (Blink), the browser process, and the UI.
HTMLPermissionElement: The Renderer ComponentThe renderer-side implementation is responsible for parsing the <permission> element, handling its attributes, and managing its state.
third_party/blink/renderer/core/html/html_permission_element.h / .cc
HTMLPermissionElement class: This is the C++ implementation of the <permission> element. It inherits from HTMLElement and handles all renderer-side logic for the element.type attribute, which specifies the permission to be requested (e.g., “camera”, “geolocation”).PermissionService Mojo interface.The HTMLPermissionElement has several complex areas in its implementation, primarily due to the need for robust security and state management.
The most complex part of the HTMLPermissionElement is its defense against clickjacking. This is achieved through a combination of an IntersectionObserver and geometry checks.
IntersectionObserver: The element uses an IntersectionObserver to monitor its visibility and occlusion, which is initialized in HTMLPermissionElement::AttachLayoutTree. The observer is configured to track whether the element is at least 90% visible and has been for at least 100ms. The callback for the observer is HTMLPermissionElement::OnIntersectionChanged.HTMLPermissionElement::DidRecalcStyle and HTMLPermissionElement::DidFinishLifecycleUpdate methods check if the element's intersection with the viewport has changed. If it has, a timer is started by calling DisableClickingTemporarily, and clicks are ignored until the timer has finished.IntersectionObserver is configured with expose_occluder_id = true in its parameters. This allows the element to know which element is covering it via the IntersectionObserverEntry in the OnIntersectionChanged callback.The HTMLPermissionElement has a complex state machine that tracks the permission status, the element‘s validity, and the user’s interaction with the element.
OnPermissionStatusChange method, which is part of the CachedPermissionStatus::Client interface implemented by HTMLPermissionElement.isValid() method returns the element's validity, which is determined by the GetClickingEnabledState() method. This method checks for things like being in a secure context, having a valid type attribute, and having valid CSS.HTMLPermissionElement::DefaultEventHandler, it checks the pending_request_created_ timestamp to see if a request is already pending.The <permission> element is not a simple element. It's composed of a shadow DOM that contains an icon and a text span, which are created in the HTMLPermissionElement::DidAddUserAgentShadowRoot method.
HTMLPermissionIconElement, which is added to the shadow DOM. This allows for the icon to have its own dedicated logic and styling.The HTMLPermissionElement has a complex lifecycle that is tied to its communication with the browser process.
HTMLPermissionElement::InsertedInto is called, which in turn calls MaybeRegisterPageEmbeddedPermissionControl. This method is responsible for making the RegisterPageEmbeddedPermissionControl Mojo call to the browser process.permission_service_ mojo remote has a disconnect handler set in GetPermissionService() to call OnPermissionServiceConnectionFailed, which resets the service connection.HTMLPermissionElement::RemovedFrom method handles cleanup, including unregistering the element from the browser process by calling EnsureUnregisterPageEmbeddedPermissionControl.To prevent websites from styling the <permission> element in a deceptive way, the element validates its own CSS.
HTMLPermissionElement::GetCascadeFilter(), which filters out properties that are not marked as kValidForPermissionElement.HTMLPermissionElement::DidRecalcStyle method calls IsStyleValid() to check the values of the allowed CSS properties. If an invalid value is detected, the element is disabled by calling DisableClickingIndefinitely(DisableReason::kInvalidStyle).The HTMLPermissionElement includes a graceful degradation mechanism known as “fallback mode.”
type attribute is invalid or requests a permission that is not supported. This is checked in HTMLPermissionElement::AttributeChanged, and if the type is invalid, EnableFallbackMode() is called.HTMLUnknownElement. The EnableFallbackMode() method achieves this by adding an <slot> element to the user agent shadow root, which allows the element's children to render, and removing the internal permission container.The Mojo interface defines the contract for communication between the renderer and the browser process.
third_party/blink/public/mojom/permissions/permission.mojom
PermissionService interface: Implemented in the browser process by PermissionServiceImpl, this interface provides methods for the renderer to interact with the permission system. The key methods for the <permission> element are:RegisterPageEmbeddedPermissionControl: The renderer calls this to inform the browser about a new <permission> element.RequestPageEmbeddedPermission: The renderer calls this when the user clicks the <permission> element.EmbeddedPermissionControlClient interface: Implemented in the renderer process by HTMLPermissionElement, this interface allows the browser to send messages back to the permission element. The key method is:OnEmbeddedPermissionControlRegistered: Called by the browser to inform the renderer of the result of the registration process.The browser-side implementation is responsible for handling the permission logic, displaying the UI, and managing the user's decisions.
A key security and anti-abuse feature of PEPC is that it limits the number of <permission> elements that can be active on a single page at any given time. This logic is managed by the EmbeddedPermissionControlChecker class.
content/browser/permissions/embedded_permission_control_checker.h / .cc
EmbeddedPermissionControlChecker class: This class is attached to a Page object (making it “per page”). It's responsible for tracking and enforcing the instance limit for <permission> elements.client_map_ Data Structure: The core of the checker is the client_map_, a std::map that associates a set of permission types with a queue of Client objects. Each Client object represents a single <permission> element on the page.kMaxPEPCPerPage Limit: The checker enforces a hard limit of 3 active <permission> elements per permission type on a page. This is defined by the kMaxPEPCPerPage constant.<permission> element is registered, CheckPageEmbeddedPermission is called. If the number of active elements for that permission type is less than the limit, the new element is approved. Otherwise, it's added to the queue but not approved. When an active element is removed (e.g., the user navigates away or the element is removed from the DOM), its corresponding Client is removed from the queue, and the next element in the queue is approved. This creates a queuing behavior that ensures that only a limited number of elements are active at any given time.content/browser/permissions/permission_service_impl.h / .cc
PermissionServiceImpl class: This class is the browser-side implementation of the PermissionService Mojo interface.RegisterPageEmbeddedPermissionControl and RequestPageEmbeddedPermission calls from the renderer and forwarding them to the PermissionControllerImpl.RegisterPageEmbeddedPermissionControl method, it uses EmbeddedPermissionControlChecker to perform security checks on the element.PermissionServiceContext member, which provides information about the renderer frame and the browser context.content/browser/permissions/permission_controller_impl.h / .cc
PermissionControllerImpl class: This is the central class for permission management in the browser process. It handles permission requests by creating PermissionRequest objects, queries permission statuses, and stores the user's decisions.The UI components are responsible for displaying the secondary permission prompt to the user. The logic for determining which UI screen to show is shared between platforms, while the view implementation is platform-specific.
EmbeddedPermissionPromptFlowModel: Shared UI LogicThe core logic for the embedded permission prompt's multi-screen flow is managed by the EmbeddedPermissionPromptFlowModel. This class is not a UI view itself, but a shared model used by both desktop and Android to decide which screen (or “variant”) of the prompt to display.
components/permissions/embedded_permission_prompt_flow_model.h / .cc
ContentSetting, OS-level status, enterprise policies) to determine the appropriate UI.Variant, which represents all possible screens the prompt can show. This includes states like kAsk (the main prompt), kPreviouslyDenied (a prompt variant for previously denied permissions), kOsPrompt (informing the user that an OS prompt is active), and kAdministratorDenied (informing the user that the permission is blocked by policy).DeterminePromptVariant method to decide which view to present to the user. This keeps the complex flow logic consistent across platforms.chrome/browser/ui/views/permissions/permission_prompt_factory.cc
CreatePermissionPrompt function: This factory function is responsible for creating the correct type of permission prompt.<permission> element by calling permissions::PermissionUtil::ShouldCurrentRequestUsePermissionElementSecondaryUI. If it was, it creates an EmbeddedPermissionPrompt.chrome/browser/ui/views/permissions/embedded_permission_prompt.h / .cc
EmbeddedPermissionPrompt class: This is the high-level controller for the embedded permission prompt. It's not a view itself, but it manages the lifecycle of the prompt view.EmbeddedPermissionPromptFlowModel is used to manage the different views that can be shown in the prompt. This includes the main prompt view, a view that informs the user that the permission is blocked, and a view that informs the user that the permission has been granted. This state management adds a layer of complexity to the prompt's implementation.Accept() and Dismiss() on the PermissionRequest delegate.chrome/browser/ui/views/permissions/embedded_permission_prompt_base_view.h / .cc
EmbeddedPermissionPromptBaseView class: This is the abstract base class for the actual UI of the embedded permission prompt. It's a views::BubbleDialogDelegateView that provides the common structure for the prompt.EmbeddedPermissionPromptBaseView is a highly dynamic view. The text and buttons that are displayed can change based on the state of the permission request. This is handled by the GetRequestLinesConfiguration() and GetButtonsConfiguration() methods, which are implemented by subclasses.On Android, the permission prompt is implemented as a modal dialog. The differentiation between a regular prompt and a PEPC prompt is handled by a boolean flag that is passed from the C++ side to the Java side.
components/permissions/android/permission_prompt/permission_prompt_android.cc
PermissionPromptAndroid class: This is the C++ part of the Android permission prompt. It acts as a bridge to the Java UI.permissions::PermissionUtil::ShouldCurrentRequestUsePermissionElementSecondaryUI() to determine if the current permission request is for a PEPC prompt.useRequestElement) to the Java PermissionDialogController when it's created via a JNI call.PermissionDialogController class: This is the main controller for the permission prompt on Android.useRequestElement boolean in its constructor. This flag is then used to customize the dialog for a PEPC prompt (e.g., by changing the text or the layout).PermissionDialogController has to carefully manage the lifecycle of the PermissionDialog. It needs to handle cases where the dialog is dismissed by the user, the system, or by a screen rotation.mNativePermissionDialogController). This involves passing data between the Java and C++ worlds, which can be complex and error-prone.PermissionDialogDelegate class: This class acts as a bridge between the PermissionRequest (from the C++ side) and the Java UI. It provides the PermissionDialogController with the necessary data to display the prompt (e.g., getMessageText(), getPrimaryButtonText()) and forwards user decisions back to the PermissionRequest (e.g., onAccept(), onDismiss()).The view for the permission prompt is defined in the layout files permission_dialog_vertical_buttons_permission.xml and permission_dialog.xml, and is inflated by the PermissionDialogCoordinator.
This section describes the step-by-step code flow for a typical permission request initiated by a <permission> element.
Element Creation (Renderer):
<permission> tag, it creates an HTMLPermissionElement instance.MaybeRegisterPageEmbeddedPermissionControl method is called.PermissionService Mojo remote and calls RegisterPageEmbeddedPermissionControl, passing the requested permissions and a pending remote for its EmbeddedPermissionControlClient implementation.Registration (Browser):
PermissionServiceImpl receives the RegisterPageEmbeddedPermissionControl call.EmbeddedPermissionControlChecker to verify that the element is allowed in the current context.OnEmbeddedPermissionControlRegistered on the EmbeddedPermissionControlClient remote to send the result back to the renderer.User Interaction (Renderer):
<permission> element.HTMLPermissionElement's DefaultEventHandler calls RequestPageEmbeddedPermissions.RequestPageEmbeddedPermission on the PermissionService Mojo remote, passing the permission descriptors and the element's position.Prompt Creation (Browser):
PermissionServiceImpl receives the RequestPageEmbeddedPermission call.PermissionControllerImpl.PermissionControllerImpl creates a PermissionRequest and shows it via the PermissionRequestManager.PermissionRequestManager creates a PermissionPromptAndroid instance.PermissionPromptAndroid constructor, permissions::PermissionUtil::ShouldCurrentRequestUsePermissionElementSecondaryUI() is called.PermissionPromptAndroid then creates a PermissionDialogController on the Java side, passing the boolean result of the check.Prompt Display (Browser):
EmbeddedPermissionPrompt (the controller) creates a concrete subclass of EmbeddedPermissionPromptBaseView (the view). The view is shown as a bubble anchored to the position of the <permission> element.PermissionDialogDelegate holds the information about whether this is a PEPC prompt. The PermissionDialogController passes this delegate to the PermissionDialogCoordinator, which then inflates and customizes the appropriate view, for example by using a different layout.User Decision (Browser):
EmbeddedPermissionPromptBaseView calls a method on its delegate, the EmbeddedPermissionPrompt. The EmbeddedPermissionPrompt calls the appropriate method on its delegate_ (the PermissionRequest), such as Accept() or Deny().PermissionDialogMediator, which notifies the PermissionDialogController. The controller then calls the appropriate method on the PermissionDialogDelegate.Decision Handling (Browser and Renderer):
PermissionRequest notifies the PermissionControllerImpl of the user's decision.PermissionControllerImpl stores the decision.RequestPageEmbeddedPermission Mojo call is sent back to the renderer, and the HTMLPermissionElement's OnEmbeddedPermissionsDecided method is called with the result.The most significant difference is that PEPC requests bypass the “quiet” permission UI. The quiet UI is a less intrusive prompt that is shown when the browser determines that a regular permission prompt would be too disruptive.
PEPC permission requests are treated differently from regular permission requests in a few key ways, primarily because they are considered a stronger signal of user intent.
A key difference is that PEPC prompts can be shown even if the permission has been previously denied by the user. Regular permission prompts are only shown if the permission status is “ask”.
Logic: Both regular and PEPC permission requests flow through the same initial path, eventually reaching PermissionContextBase::RequestPermission for the specific permission type. Inside this method, the following sequence occurs:
GetPermissionStatus is called to retrieve the current status of the permission (e.g., GRANTED, DENIED, or ASK). This function also returns the source of the status (e.g., user decision, embargo, kill switch).PermissionUtil::CanPermissionRequestIgnoreStatus. This is the critical function for the bypass behavior.CanPermissionRequestIgnoreStatus checks if the request was initiated by a permission element (via the embedded_permission_element_initiated flag in PermissionRequestData).source of the permission status. It returns true if the denial is from a “soft” source that a direct user gesture should be able to override (like a previous user dismissal or ignore). It returns false for “hard” denials (like a kill switch or enterprise policy).PermissionContextBase::RequestPermission, there is a check: if (!status_ignorable && ...)CanPermissionRequestIgnoreStatus returns false. If the permission is already denied, the condition is met, and the request is immediately rejected without showing a prompt.CanPermissionRequestIgnoreStatus returns true. This makes the !status_ignorable part of the condition false, so the entire block is skipped.DecidePermission, which continues the process of showing a prompt to the user.Rationale: This allows users to easily change their minds about a permission that they have previously denied, without having to go into site settings. The explicit user gesture on the <permission> element is considered a strong enough signal to warrant re-prompting, unless the permission is blocked by a system-level or administrative policy.
The most significant difference is that PEPC requests bypass the “quiet” permission UI. The quiet UI is a less intrusive prompt that is shown when the browser determines that a regular permission prompt would be too disruptive.
PermissionManager::RequestPermissions. This method checks if the request is a PEPC request by calling permissions::PermissionUtil::ShouldCurrentRequestUsePermissionElementSecondaryUI. If it is, the quiet UI is skipped and the full permission prompt is shown.<permission> element, it's assumed that the user is expecting a prompt and is ready to make a decision. Therefore, the less intrusive quiet UI is not necessary.As mentioned in the previous sections, the UI for a PEPC prompt is different from a regular permission prompt.
EmbeddedPermissionPrompt, which is a bubble that is anchored to the <permission> element. A regular permission prompt is a bubble that is anchored to the omnibox.PermissionDialogController receives a useRequestElement boolean that tells it to customize the dialog for a PEPC request. This can include changing the text to be more contextual to the user's action, or using a different layout.PEPC requests have a higher priority than regular permission requests. If a regular permission prompt is already showing and a PEPC request is triggered, the regular prompt will be cancelled and the PEPC prompt will be shown immediately.
PermissionRequestQueue::AddRequest. When a new request is added to the queue, it checks if the new request is a PEPC request and if the currently showing request is not a PEPC request. If both are true, the currently showing request is cancelled.The PEPC feature is instrumented with a number of metrics to monitor its usage and performance. These metrics are recorded in UMA and UKM.
These metrics are recorded in the renderer process and are related to the <permission> element itself.
Blink.PermissionElement.UserInteractionAccepted: A boolean histogram that records whether a user interaction (click) on the <permission> element was accepted or not. A click might be rejected for security reasons, for example if the element is obscured or has a deceptive style.
Blink.PermissionElement.UserInteractionDeniedReason: An enumeration histogram that records the reason why a user interaction was denied. The possible values are:
kInvalidType: The type attribute of the element is invalid.kFailedOrHasNotBeenRegistered: The element has not been successfully registered with the browser process.kRecentlyAttachedToLayoutTree: The element was recently attached to the layout tree.kIntersectionWithViewportChanged: The element's intersection with the viewport has recently changed.kIntersectionVisibilityOutOfViewPortOrClipped: The element is outside the viewport or clipped.kIntersectionVisibilityOccludedOrDistorted: The element is occluded by another element or has a distorted visual effect.kInvalidStyle: The element has a deceptive style.kUntrustedEvent: The click event was not triggered by a real user.Blink.PermissionElement.InvalidStyleReason: An enumeration histogram that records the reason why the element's style was considered invalid.
These metrics are recorded in the browser process and are related to the PEPC prompt.
Permissions.EmbeddedPermissionPrompt.Flow.Variant: An enumeration histogram that records which variant of the PEPC prompt was shown to the user. The possible values are:
kUninitializedkAdministratorGrantedkPreviouslyGrantedkOsSystemSettingskOsPromptkAskkPreviouslyDeniedkAdministratorDeniedPermissions.Prompt.{PermissionType}.ElementAnchoredBubble.{OsScreen}.OsScreenAction: An enumeration histogram that records the user's action on the OS-related screens of the PEPC prompt.
{PermissionType} can be Camera, Microphone, or Geolocation.{OsScreen} can be OsPrompt or OsSystemSettings.OsScreenAction can be:kSystemSettingskDismissedXButtonkDismissedScrimkOsPromptDeniedkOsPromptAllowedPermissions.Prompt.{PermissionType}.ElementAnchoredBubble.{OsScreen}.{OsScreenAction}.TimeToAction: A time histogram that records the time between showing an OS-related screen and the user taking an action on it.
UKM data is recorded for the PEPC feature to allow for more detailed analysis of user behavior. The ElementAnchoredPermissionPrompt event is recorded with the following metrics:
Action: The user's action on the prompt. The possible values are defined in the ElementAnchoredBubbleAction enum.Variant: The variant of the prompt that was shown. The possible values are defined in the ElementAnchoredBubbleVariant enum.ScreenCounter: The number of screens that were shown to the user during the permission request flow.