0

[PEPC] Snapshot permission statuses at commit navigation

When <permission> element is added to the DOM, we'll use IPC to check
the permission status and it'll update when the IPC callback is
triggered. This might cause a flicker if the status changes from ASK
to GRANTED.

We propose to snapshot permission statuses and cache the statuses at
the execution context, outlined in: https://docs.google.com/document/d/1pfuiJ7BIdX0dxMzboYIOvPV1mFKQ3HE_11VQNeBJSCc/edit?resourcekey=0-9el74tCe5opJXh3CIPyPGA&tab=t.0#heading=h.8zmwced28dbp

Bug: 368238224
Change-Id: Ib319301d0b9bedb07502a09b4f030d3e40844ea2
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/5905369
Reviewed-by: Thomas Nguyen <tungnh@chromium.org>
Commit-Queue: Thomas Nguyen <tungnh@chromium.org>
Reviewed-by: Dave Tapuska <dtapuska@chromium.org>
Reviewed-by: Daniel Cheng <dcheng@chromium.org>
Reviewed-by: Francois Pierre Doray <fdoray@chromium.org>
Auto-Submit: Thomas Nguyen <tungnh@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1372969}
This commit is contained in:
Thomas Nguyen
2024-10-23 21:54:25 +00:00
committed by Chromium LUCI CQ
parent 34981def10
commit 1924a27af3
22 changed files with 758 additions and 73 deletions

@ -253,8 +253,8 @@ TEST_P(PerformanceManagerTabHelperTest, PageIsAudible) {
#if !BUILDFLAG(IS_ANDROID)
TEST_P(PerformanceManagerTabHelperTest, NotificationPermission) {
auto owned_permission_controller = std::make_unique<
testing::StrictMock<content::MockPermissionController>>();
auto owned_permission_controller =
std::make_unique<testing::NiceMock<content::MockPermissionController>>();
auto* permission_controller = owned_permission_controller.get();
GetBrowserContext()->SetPermissionControllerForTesting(
std::move(owned_permission_controller));

@ -130,6 +130,9 @@ class CONTENT_EXPORT PermissionControllerImpl : public PermissionController {
PermissionResult GetPermissionResultForCurrentDocument(
PermissionType permission,
RenderFrameHost* render_frame_host) override;
PermissionStatus GetCombinedPermissionAndDeviceStatus(
PermissionType permission,
RenderFrameHost* render_frame_host) override;
PermissionResult GetPermissionResultForOriginWithoutContext(
PermissionType permission,
const url::Origin& origin) override;
@ -165,13 +168,6 @@ class CONTENT_EXPORT PermissionControllerImpl : public PermissionController {
RenderFrameHost* render_frame_host,
const url::Origin& requesting_origin);
// The method does the same as `GetPermissionStatusForCurrentDocument` but it
// also takes into account the device's status (OS permission status).
// Currently, this function is only used for Page Embedded Permission Control.
PermissionStatus GetCombinedPermissionAndDeviceStatus(
PermissionType permission,
RenderFrameHost* render_frame_host);
using SubscriptionsStatusMap =
base::flat_map<SubscriptionsMap::KeyType, PermissionStatus>;

@ -4111,7 +4111,8 @@ NavigationControllerImpl::CreateNavigationRequestFromLoadParams(
/*lcpp_hint=*/nullptr, blink::CreateDefaultRendererContentSettings(),
/*cookie_deprecation_label=*/std::nullopt,
/*visited_link_salt=*/std::nullopt,
/*local_surface_id=*/std::nullopt);
/*local_surface_id=*/std::nullopt,
node->current_frame_host()->GetCachedPermissionStatuses());
#if BUILDFLAG(IS_ANDROID)
if (ValidateDataURLAsString(params.data_url_as_string)) {
commit_params->data_url_as_string = params.data_url_as_string->as_string();
@ -4252,6 +4253,8 @@ NavigationControllerImpl::CreateNavigationRequestFromEntry(
commit_params->navigation_timing->system_entropy_at_navigation_start =
SystemEntropyUtils::ComputeSystemEntropyForFrameTreeNode(
frame_tree_node, blink::mojom::SystemEntropy::kNormal);
commit_params->initial_permission_statuses =
frame_tree_node->current_frame_host()->GetCachedPermissionStatuses();
if (common_params->url.IsAboutSrcdoc()) {
// TODO(wjmaclean): initialize this in NavigationRequest's constructor

@ -1001,7 +1001,8 @@ NavigationEntryImpl::ConstructCommitNavigationParams(
/*lcpp_hint=*/nullptr, blink::CreateDefaultRendererContentSettings(),
/*cookie_deprecation_label=*/std::nullopt,
/*visited_link_salt=*/std::nullopt,
/*local_surface_id=*/std::nullopt);
/*local_surface_id=*/std::nullopt,
/*initial_permission_statuses=*/std::nullopt);
#if BUILDFLAG(IS_ANDROID)
// `data_url_as_string` is saved in NavigationEntry but should only be used by
// main frames, because loadData* navigations can only happen on the main

@ -64,6 +64,7 @@
#include "content/browser/network_service_instance_impl.h"
#include "content/browser/origin_agent_cluster_isolation_state.h"
#include "content/browser/origin_trials/origin_trials_utils.h"
#include "content/browser/permissions/permission_controller_impl.h"
#include "content/browser/preloading/prefetch/prefetch_document_manager.h"
#include "content/browser/preloading/prefetch/prefetch_features.h"
#include "content/browser/preloading/prefetch/prefetch_serving_page_metrics_container.h"
@ -1431,7 +1432,8 @@ std::unique_ptr<NavigationRequest> NavigationRequest::CreateRendererInitiated(
/*lcpp_hint=*/nullptr, blink::CreateDefaultRendererContentSettings(),
/*cookie_deprecation_label=*/std::nullopt,
/*visited_link_salt=*/std::nullopt,
/*local_surface_id=*/std::nullopt);
/*local_surface_id=*/std::nullopt,
frame_tree_node->current_frame_host()->GetCachedPermissionStatuses());
commit_params->navigation_timing->system_entropy_at_navigation_start =
SystemEntropyUtils::ComputeSystemEntropyForFrameTreeNode(
@ -1580,7 +1582,8 @@ NavigationRequest::CreateForSynchronousRendererCommit(
/*lcpp_hint=*/nullptr, blink::CreateDefaultRendererContentSettings(),
/*cookie_deprecation_label=*/std::nullopt,
/*visited_link_salt=*/std::nullopt,
/*local_surface_id=*/std::nullopt);
/*local_surface_id=*/std::nullopt,
render_frame_host->GetCachedPermissionStatuses());
blink::mojom::BeginNavigationParamsPtr begin_params =
blink::mojom::BeginNavigationParams::New();
std::unique_ptr<NavigationRequest> navigation_request(new NavigationRequest(

@ -17657,4 +17657,40 @@ void RenderFrameHostImpl::GetBoundInterfacesForTesting(
broker_.GetBinderMapInterfacesForTesting(out); // IN-TEST
}
std::optional<base::flat_map<blink::mojom::PermissionName,
blink::mojom::PermissionStatus>>
RenderFrameHostImpl::GetCachedPermissionStatuses() {
// `GetCombinedPermissionStatus` on Android is not fully supported for now.
#if BUILDFLAG(IS_ANDROID)
return std::nullopt;
#else
using blink::PermissionType;
using blink::mojom::PermissionName;
static constexpr auto kPermissions =
std::to_array<std::pair<PermissionName, PermissionType>>(
{{PermissionName::VIDEO_CAPTURE, PermissionType::VIDEO_CAPTURE},
{PermissionName::AUDIO_CAPTURE, PermissionType::AUDIO_CAPTURE},
{PermissionName::GEOLOCATION, PermissionType::GEOLOCATION}});
base::flat_map<PermissionName, PermissionStatus> permission_map;
for (const auto& permission : kPermissions) {
PermissionStatus status = GetCombinedPermissionStatus(permission.second);
// Default value is ASK, we don't need add the permission status in this
// case.
if (status != PermissionStatus::ASK) {
permission_map.emplace(permission.first, status);
}
}
return permission_map;
#endif // !BUILDFLAG(IS_ANDROID)
}
blink::mojom::PermissionStatus RenderFrameHostImpl::GetCombinedPermissionStatus(
blink::PermissionType permission_type) {
return GetBrowserContext()
->GetPermissionController()
->GetCombinedPermissionAndDeviceStatus(permission_type, this);
}
} // namespace content

@ -306,6 +306,10 @@ typedef base::RepeatingCallback<
void(RenderFrameHostImpl*, ax::mojom::Event, int)>
AccessibilityCallbackForTesting;
using CachedPermissionMap =
std::optional<base::flat_map<blink::mojom::PermissionName,
blink::mojom::PermissionStatus>>;
class CONTENT_EXPORT RenderFrameHostImpl
: public RenderFrameHost,
public base::SupportsUserData,
@ -3118,6 +3122,12 @@ class CONTENT_EXPORT RenderFrameHostImpl
// this instance's AXNodeIdDelegate implementation.
size_t GetAxUniqueIdCountForTesting() const { return ax_unique_ids_.size(); }
// Query necessary permission statues in order to propagate to the renderer.
// Right now, we're only caring about permissions for Geolocation, Camera, and
// Microphone. The permission statuses already take into account the device's
// status.
CachedPermissionMap GetCachedPermissionStatuses();
// Allows tests to disable the unload event timer to simulate bugs that
// happen before it fires (to avoid flakiness).
void DisableUnloadTimerForTesting();
@ -4223,6 +4233,9 @@ class CONTENT_EXPORT RenderFrameHostImpl
// keep-alive requests a chance to resolve before timing out.
void CleanupRenderProcessForDiscardIfPossible();
blink::mojom::PermissionStatus GetCombinedPermissionStatus(
blink::PermissionType permission_type);
// The RenderViewHost that this RenderFrameHost is associated with.
//
// It is kept alive as long as any RenderFrameHosts or RenderFrameProxyHosts

@ -68,6 +68,13 @@ class CONTENT_EXPORT PermissionController
blink::PermissionType permission,
RenderFrameHost* render_frame_host) = 0;
// The method does the same as `GetPermissionStatusForCurrentDocument` but it
// also takes into account the device's status (OS permission status).
// Currently, this function is only used for Page Embedded Permission Control.
virtual PermissionStatus GetCombinedPermissionAndDeviceStatus(
blink::PermissionType permission,
RenderFrameHost* render_frame_host) = 0;
// Returns the permission status for a given origin. Use this API only if
// there is no document and it is not a ServiceWorker.
virtual PermissionResult GetPermissionResultForOriginWithoutContext(

@ -43,6 +43,10 @@ class MockPermissionController : public PermissionController {
GetPermissionResultForCurrentDocument,
(blink::PermissionType permission,
RenderFrameHost* render_frame_host));
MOCK_METHOD(blink::mojom::PermissionStatus,
GetCombinedPermissionAndDeviceStatus,
(blink::PermissionType permission,
RenderFrameHost* render_frame_host));
MOCK_METHOD(content::PermissionResult,
GetPermissionResultForOriginWithoutContext,
(blink::PermissionType permission,

@ -1105,6 +1105,8 @@ void FillMiscNavigationParams(
navigation_params->cookie_deprecation_label =
WebString::FromASCII(*commit_params.cookie_deprecation_label);
}
navigation_params->initial_permission_statuses =
std::move(commit_params.initial_permission_statuses);
}
std::string GetUniqueNameOfWebFrame(WebFrame* web_frame) {

@ -38,6 +38,8 @@ import "third_party/blink/public/mojom/navigation/system_entropy.mojom";
import "third_party/blink/public/mojom/navigation/was_activated_option.mojom";
import "third_party/blink/public/mojom/page/browsing_context_group_info.mojom";
import "third_party/blink/public/mojom/page/page.mojom";
import "third_party/blink/public/mojom/permissions/permission.mojom";
import "third_party/blink/public/mojom/permissions/permission_status.mojom";
import "third_party/blink/public/mojom/permissions_policy/permissions_policy.mojom";
import "third_party/blink/public/mojom/runtime_feature_state/runtime_feature.mojom";
import "third_party/blink/public/mojom/storage_key/storage_key.mojom";
@ -658,6 +660,12 @@ struct CommitNavigationParams {
// earlier to ensure the new Document doesn't render frames using the same ID
// as the old Document when it is committed in the renderer.
viz.mojom.LocalSurfaceId? local_surface_id;
// Map of permission statuses at commit time.
// Note: the permission statues will be only used as initial states of
// `CachedPermissionStatus` in renderer side.
// Could be null for synchronous commit, same document navigations.
map<PermissionName, PermissionStatus>? initial_permission_statuses;
};
// Parameters used to dispatch the `pageswap` event on the old Document for

@ -576,6 +576,13 @@ struct BLINK_EXPORT WebNavigationParams {
// the VisitedLinkNotificationSink interface) after the :visited link
// hashtable is initialized.
std::optional<uint64_t> visited_link_salt;
// Map of permission statuses at commit time.
// Note: the permission statues will be only used as initial states of
// `CachedPermissionStatus` in renderer side.
// Could be std::nullopt for synchronous commit, same document navigations.
std::optional<base::flat_map<mojom::PermissionName, mojom::PermissionStatus>>
initial_permission_statuses;
};
} // namespace blink

@ -17,6 +17,8 @@ blink_core_sources_frame = [
"bar_prop.h",
"browser_controls.cc",
"browser_controls.h",
"cached_permission_status.cc",
"cached_permission_status.h",
"coop_access_violation_report_body.cc",
"coop_access_violation_report_body.h",
"csp/content_security_policy.cc",
@ -257,6 +259,7 @@ blink_core_tests_frame = [
"ad_tracker_test.cc",
"attribution_src_loader_test.cc",
"browser_controls_test.cc",
"cached_permission_status_test.cc",
"child_frame_compositing_helper_test.cc",
"csp/content_security_policy_test.cc",
"csp/conversion_util_test.cc",

@ -0,0 +1,130 @@
// Copyright 2024 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "third_party/blink/renderer/core/frame/cached_permission_status.h"
#include "third_party/blink/renderer/core/execution_context/execution_context.h"
#include "third_party/blink/renderer/core/frame/local_dom_window.h"
#include "third_party/blink/renderer/platform/runtime_enabled_features.h"
namespace blink {
using mojom::blink::PermissionDescriptor;
using mojom::blink::PermissionDescriptorPtr;
using mojom::blink::PermissionName;
using mojom::blink::PermissionObserver;
using mojom::blink::PermissionService;
using mojom::blink::PermissionStatus;
// static
const char CachedPermissionStatus::kSupplementName[] = "CachedPermissionStatus";
// static
CachedPermissionStatus* CachedPermissionStatus::From(LocalDOMWindow* window) {
CachedPermissionStatus* cache =
Supplement<LocalDOMWindow>::From<CachedPermissionStatus>(window);
if (!cache) {
cache = MakeGarbageCollected<CachedPermissionStatus>(window);
ProvideTo(*window, cache);
}
return cache;
}
CachedPermissionStatus::CachedPermissionStatus(LocalDOMWindow* local_dom_window)
: Supplement<LocalDOMWindow>(*local_dom_window),
permission_service_(local_dom_window),
permission_observer_receivers_(this, local_dom_window) {
CHECK(local_dom_window);
CHECK(RuntimeEnabledFeatures::PermissionElementEnabled(local_dom_window));
}
void CachedPermissionStatus::Trace(Visitor* visitor) const {
visitor->Trace(permission_service_);
visitor->Trace(permission_observer_receivers_);
visitor->Trace(clients_);
Supplement<LocalDOMWindow>::Trace(visitor);
}
void CachedPermissionStatus::RegisterClient(
Client* client,
const Vector<PermissionDescriptorPtr>& permissions) {
for (const PermissionDescriptorPtr& descriptor : permissions) {
auto status_it = permission_status_map_.find(descriptor->name);
PermissionStatus status = status_it != permission_status_map_.end()
? status_it->value
: PermissionStatus::ASK;
client->OnPermissionStatusInitialized(descriptor->name, status);
auto client_it = clients_.find(descriptor->name);
if (client_it != clients_.end()) {
auto inserted = client_it->value.insert(client);
CHECK(inserted.is_new_entry);
continue;
}
HeapHashSet<WeakMember<Client>> client_set;
client_set.insert(client);
clients_.insert(descriptor->name, std::move(client_set));
RegisterPermissionObserver(descriptor, status);
}
}
void CachedPermissionStatus::UnregisterClient(
Client* client,
const Vector<PermissionDescriptorPtr>& permissions) {
for (const PermissionDescriptorPtr& descriptor : permissions) {
auto it = clients_.find(descriptor->name);
CHECK(it != clients_.end());
HeapHashSet<WeakMember<Client>>& client_set = it->value;
auto client_set_it = client_set.find(client);
CHECK(client_set_it != client_set.end());
client_set.erase(client_set_it);
if (!client_set.empty()) {
continue;
}
clients_.erase(it);
// Stop listening changes in permissions for a permission name, if there's
// no client that matches that name.
auto receiver_it = permission_to_receivers_map_.find(descriptor->name);
CHECK(receiver_it != permission_to_receivers_map_.end());
permission_observer_receivers_.Remove(receiver_it->value);
permission_to_receivers_map_.erase(receiver_it);
}
}
void CachedPermissionStatus::RegisterPermissionObserver(
const PermissionDescriptorPtr& descriptor,
PermissionStatus current_status) {
mojo::PendingRemote<PermissionObserver> observer;
mojo::ReceiverId id = permission_observer_receivers_.Add(
observer.InitWithNewPipeAndPassReceiver(), descriptor->name,
GetTaskRunner());
GetPermissionService()->AddPageEmbeddedPermissionObserver(
descriptor.Clone(), current_status, std::move(observer));
auto inserted = permission_to_receivers_map_.insert(descriptor->name, id);
CHECK(inserted.is_new_entry);
}
void CachedPermissionStatus::OnPermissionStatusChange(PermissionStatus status) {
permission_status_map_.Set(permission_observer_receivers_.current_context(),
status);
}
PermissionService* CachedPermissionStatus::GetPermissionService() {
if (!permission_service_.is_bound()) {
GetSupplementable()->GetBrowserInterfaceBroker().GetInterface(
permission_service_.BindNewPipeAndPassReceiver(GetTaskRunner()));
}
return permission_service_.get();
}
scoped_refptr<base::SingleThreadTaskRunner>
CachedPermissionStatus::GetTaskRunner() {
return GetSupplementable()->GetTaskRunner(TaskType::kInternalDefault);
}
} // namespace blink

@ -0,0 +1,149 @@
// Copyright 2024 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef THIRD_PARTY_BLINK_RENDERER_CORE_FRAME_CACHED_PERMISSION_STATUS_H_
#define THIRD_PARTY_BLINK_RENDERER_CORE_FRAME_CACHED_PERMISSION_STATUS_H_
#include "base/memory/scoped_refptr.h"
#include "base/task/single_thread_task_runner.h"
#include "third_party/blink/public/mojom/permissions/permission.mojom-blink.h"
#include "third_party/blink/renderer/core/core_export.h"
#include "third_party/blink/renderer/platform/graphics/dom_node_id.h"
#include "third_party/blink/renderer/platform/heap/collection_support/heap_hash_map.h"
#include "third_party/blink/renderer/platform/heap/collection_support/heap_hash_set.h"
#include "third_party/blink/renderer/platform/heap/garbage_collected.h"
#include "third_party/blink/renderer/platform/heap/member.h"
#include "third_party/blink/renderer/platform/mojo/heap_mojo_receiver_set.h"
#include "third_party/blink/renderer/platform/mojo/heap_mojo_remote.h"
#include "third_party/blink/renderer/platform/wtf/forward.h"
namespace blink {
class LocalDOMWindow;
// This cache keeps track of permission statuses, restricted to the permission
// element. These permission statuses are not canonical and should not be used
// for actual permission checks.
// - It is initialized by an initial set of permission statuses passed down in
// CommitNavigationParams.
// - Only registered permission elements can access this cache data.
// - When there are permission elements registered, the cache registers itself
// for permission changes from the browser. And when there aren't any
// permission elements, it unregisters itself from permission updates.
class CORE_EXPORT CachedPermissionStatus final
: public GarbageCollected<CachedPermissionStatus>,
public mojom::blink::PermissionObserver,
public Supplement<LocalDOMWindow> {
public:
static const char kSupplementName[];
// Returns the supplement, creating one as needed.
static CachedPermissionStatus* From(LocalDOMWindow* window);
// Instances of this class receives notification from the cache, for example
// receives any status changes notification. Notifications are only sent back
// to the instances that have been registered first by calling RegisterClient.
class Client : public GarbageCollectedMixin {
public:
virtual ~Client() = default;
// TODO(crbug.com/368238224): Only listen permission status change here and
// notify client. Then move the PermissionObserver from
// HTMLPermissionElement to here, to remove all the PermissionObserver
// duplicate IPC calls.
virtual void OnPermissionStatusChanged(
mojom::blink::PermissionStatus status) {}
virtual void OnPermissionStatusInitialized(
mojom::blink::PermissionName permission,
mojom::blink::PermissionStatus status) = 0;
};
using PermissionStatusMap =
HashMap<mojom::blink::PermissionName, mojom::blink::PermissionStatus>;
explicit CachedPermissionStatus(LocalDOMWindow* local_dom_window);
~CachedPermissionStatus() override = default;
void Trace(Visitor* visitor) const override;
void SetPermissionStatusMap(PermissionStatusMap map) {
permission_status_map_ = std::move(map);
}
private:
friend class HTMLPermissionElement;
friend class DocumentLoader;
friend class CachedPermissionStatusTest;
FRIEND_TEST_ALL_PREFIXES(CachedPermissionStatusTest, RegisterClient);
FRIEND_TEST_ALL_PREFIXES(CachedPermissionStatusTest,
UnregisterClientRemoveObserver);
// Allow this object to keep track of the Client instances corresponding to
// it.
void RegisterClient(
Client* client,
const Vector<mojom::blink::PermissionDescriptorPtr>& permissions);
void UnregisterClient(
Client* client,
const Vector<mojom::blink::PermissionDescriptorPtr>& permissions);
void RegisterPermissionObserver(
const mojom::blink::PermissionDescriptorPtr& descriptor,
mojom::blink::PermissionStatus current_status);
// mojom::blink::PermissionObserver override.
void OnPermissionStatusChange(mojom::blink::PermissionStatus status) override;
// Ensure there is a connection to the permission service and return it.
mojom::blink::PermissionService* GetPermissionService();
scoped_refptr<base::SingleThreadTaskRunner> GetTaskRunner();
const PermissionStatusMap& GetPermissionStatusMap() const {
return permission_status_map_;
}
using ClientMap = HeapHashMap<mojom::blink::PermissionName,
HeapHashSet<WeakMember<Client>>>;
using PermissionToReceiversMap =
HashMap<mojom::blink::PermissionName, mojo::ReceiverId>;
using PermissionObserverReceiverSet =
HeapMojoReceiverSet<mojom::blink::PermissionObserver,
CachedPermissionStatus,
HeapMojoWrapperMode::kWithContextObserver,
mojom::blink::PermissionName>;
const ClientMap& GetClientsForTesting() const { return clients_; }
const PermissionToReceiversMap& GetPermissionToReceiversMapForTesting()
const {
return permission_to_receivers_map_;
}
PermissionObserverReceiverSet& GetPermissionObserverReceiversForTesting() {
return permission_observer_receivers_;
}
HeapMojoRemote<mojom::blink::PermissionService> permission_service_;
// Holds all `PermissionObserver` receivers connected with remotes in browser
// process.
// This set uses `PermissionName` as context type. Once a receiver call is
// triggered, we look into its name to determine which permission is changed.
PermissionObserverReceiverSet permission_observer_receivers_;
ClientMap clients_;
// Track which `permission` are in the `permission_observer_receivers_` set so
// they can be removed them when necessary.
PermissionToReceiversMap permission_to_receivers_map_;
PermissionStatusMap permission_status_map_;
};
} // namespace blink
#endif // THIRD_PARTY_BLINK_RENDERER_CORE_FRAME_CACHED_PERMISSION_STATUS_H_

@ -0,0 +1,182 @@
// Copyright 2024 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "third_party/blink/renderer/core/frame/cached_permission_status.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/blink/renderer/core/frame/local_dom_window.h"
#include "third_party/blink/renderer/core/frame/local_frame.h"
#include "third_party/blink/renderer/core/testing/page_test_base.h"
namespace blink {
using mojom::blink::PermissionDescriptor;
using mojom::blink::PermissionDescriptorPtr;
using mojom::blink::PermissionName;
using mojom::blink::PermissionObserver;
using mojom::blink::PermissionStatus;
namespace {
PermissionDescriptorPtr CreatePermissionDescriptor(PermissionName name) {
auto descriptor = PermissionDescriptor::New();
descriptor->name = name;
return descriptor;
}
Vector<PermissionDescriptorPtr> CreatePermissionDescriptors(
const AtomicString& permissions_string) {
SpaceSplitString permissions(permissions_string);
Vector<PermissionDescriptorPtr> permission_descriptors;
for (const auto& permission : permissions) {
if (permission == "geolocation") {
permission_descriptors.push_back(
CreatePermissionDescriptor(PermissionName::GEOLOCATION));
} else if (permission == "camera") {
permission_descriptors.push_back(
CreatePermissionDescriptor(PermissionName::VIDEO_CAPTURE));
} else if (permission == "microphone") {
permission_descriptors.push_back(
CreatePermissionDescriptor(PermissionName::AUDIO_CAPTURE));
}
}
return permission_descriptors;
}
} // namespace
class MockHTMLPermissionElement
: public GarbageCollected<MockHTMLPermissionElement>,
public CachedPermissionStatus::Client {
public:
MockHTMLPermissionElement() = default;
~MockHTMLPermissionElement() override = default;
void OnPermissionStatusInitialized(PermissionName permission,
PermissionStatus status) override {}
void Trace(Visitor* visitor) const override {}
};
class CachedPermissionStatusTest : public PageTestBase {
public:
CachedPermissionStatusTest() = default;
CachedPermissionStatusTest(const CachedPermissionStatusTest&) = delete;
CachedPermissionStatusTest& operator=(const CachedPermissionStatusTest&) =
delete;
void SetUp() override {
PageTestBase::SetUp();
CachedPermissionStatus::From(GetDocument().domWindow())
->SetPermissionStatusMap(HashMap<PermissionName, PermissionStatus>(
{{PermissionName::VIDEO_CAPTURE, PermissionStatus::ASK},
{PermissionName::AUDIO_CAPTURE, PermissionStatus::ASK},
{PermissionName::GEOLOCATION, PermissionStatus::ASK}}));
}
bool HasClient(PermissionName permission,
CachedPermissionStatus::Client* client) const {
CachedPermissionStatus* cache =
CachedPermissionStatus::From(GetDocument().domWindow());
const auto& clients = cache->GetClientsForTesting();
auto it = clients.find(permission);
if (it == clients.end()) {
return false;
}
const auto& client_set = it->value;
return client_set.find(client) != client_set.end();
}
bool HasPermisionObserver(PermissionName permission) const {
CachedPermissionStatus* cache =
CachedPermissionStatus::From(GetDocument().domWindow());
const auto& permission_to_receivers_map =
cache->GetPermissionToReceiversMapForTesting();
auto it = permission_to_receivers_map.find(permission);
if (it == permission_to_receivers_map.end()) {
return false;
}
mojo::ReceiverId id = it->value;
auto& permission_observer_receivers =
cache->GetPermissionObserverReceiversForTesting();
return permission_observer_receivers.HasReceiver(id);
}
};
#if BUILDFLAG(IS_ANDROID)
#define MAYBE_RegisterClient DISABLED_RegisterClient
#else
#define MAYBE_RegisterClient RegisterClient
#endif
TEST_F(CachedPermissionStatusTest, MAYBE_RegisterClient) {
auto* client1 = MakeGarbageCollected<MockHTMLPermissionElement>();
CachedPermissionStatus* cache =
CachedPermissionStatus::From(GetDocument().domWindow());
cache->RegisterClient(
client1, CreatePermissionDescriptors(AtomicString("geolocation")));
EXPECT_TRUE(HasClient(PermissionName::GEOLOCATION, client1));
EXPECT_TRUE(HasPermisionObserver(PermissionName::GEOLOCATION));
auto* client2 = MakeGarbageCollected<MockHTMLPermissionElement>();
cache->RegisterClient(
client2, CreatePermissionDescriptors(AtomicString("geolocation")));
EXPECT_TRUE(HasClient(PermissionName::GEOLOCATION, client2));
auto* client3 = MakeGarbageCollected<MockHTMLPermissionElement>();
cache->RegisterClient(client3,
CreatePermissionDescriptors(AtomicString("camera")));
EXPECT_TRUE(HasClient(PermissionName::VIDEO_CAPTURE, client3));
EXPECT_TRUE(HasPermisionObserver(PermissionName::VIDEO_CAPTURE));
auto clients = cache->GetClientsForTesting();
{
auto it = clients.find(PermissionName::GEOLOCATION);
EXPECT_TRUE(it != clients.end());
EXPECT_EQ(it->value.size(), 2u);
}
{
auto it = clients.find(PermissionName::VIDEO_CAPTURE);
EXPECT_TRUE(it != clients.end());
EXPECT_EQ(it->value.size(), 1u);
}
}
#if BUILDFLAG(IS_ANDROID)
#define MAYBE_UnregisterClientRemoveObserver \
DISABLED_UnregisterClientRemoveObserver
#else
#define MAYBE_UnregisterClientRemoveObserver UnregisterClientRemoveObserver
#endif
TEST_F(CachedPermissionStatusTest, MAYBE_UnregisterClientRemoveObserver) {
auto* client1 = MakeGarbageCollected<MockHTMLPermissionElement>();
CachedPermissionStatus* cache =
CachedPermissionStatus::From(GetDocument().domWindow());
cache->RegisterClient(
client1, CreatePermissionDescriptors(AtomicString("geolocation")));
EXPECT_TRUE(HasClient(PermissionName::GEOLOCATION, client1));
EXPECT_TRUE(HasPermisionObserver(PermissionName::GEOLOCATION));
auto* client2 = MakeGarbageCollected<MockHTMLPermissionElement>();
cache->RegisterClient(
client2, CreatePermissionDescriptors(AtomicString("geolocation")));
EXPECT_TRUE(HasClient(PermissionName::GEOLOCATION, client2));
auto* client3 = MakeGarbageCollected<MockHTMLPermissionElement>();
cache->RegisterClient(client3,
CreatePermissionDescriptors(AtomicString("camera")));
EXPECT_TRUE(HasClient(PermissionName::VIDEO_CAPTURE, client3));
EXPECT_TRUE(HasPermisionObserver(PermissionName::VIDEO_CAPTURE));
cache->UnregisterClient(
client2, CreatePermissionDescriptors(AtomicString("geolocation")));
EXPECT_TRUE(HasPermisionObserver(PermissionName::GEOLOCATION));
EXPECT_TRUE(HasPermisionObserver(PermissionName::VIDEO_CAPTURE));
cache->UnregisterClient(
client1, CreatePermissionDescriptors(AtomicString("geolocation")));
EXPECT_FALSE(HasPermisionObserver(PermissionName::GEOLOCATION));
cache->UnregisterClient(client3,
CreatePermissionDescriptors(AtomicString("camera")));
EXPECT_FALSE(HasPermisionObserver(PermissionName::VIDEO_CAPTURE));
}
} // namespace blink

@ -30,6 +30,7 @@
#include "third_party/blink/renderer/core/events/mouse_event.h"
#include "third_party/blink/renderer/core/execution_context/execution_context.h"
#include "third_party/blink/renderer/core/frame/csp/content_security_policy.h"
#include "third_party/blink/renderer/core/frame/local_dom_window.h"
#include "third_party/blink/renderer/core/frame/local_frame.h"
#include "third_party/blink/renderer/core/frame/local_frame_ukm_aggregator.h"
#include "third_party/blink/renderer/core/frame/visual_viewport.h"
@ -462,6 +463,12 @@ void HTMLPermissionElement::Trace(Visitor* visitor) const {
HTMLElement::Trace(visitor);
}
void HTMLPermissionElement::OnPermissionStatusInitialized(
mojom::blink::PermissionName permission,
mojom::blink::PermissionStatus status) {
permission_status_map_.Set(permission, status);
}
Node::InsertionNotificationRequest HTMLPermissionElement::InsertedInto(
ContainerNode& insertion_point) {
HTMLElement::InsertedInto(insertion_point);
@ -501,6 +508,15 @@ Node::InsertionNotificationRequest HTMLPermissionElement::InsertedInto(
if (embedded_permission_control_receiver_.is_bound()) {
return kInsertionDone;
}
if (LocalDOMWindow* window = GetDocument().domWindow()) {
CachedPermissionStatus::From(window)->RegisterClient(
this, permission_descriptors_);
UpdatePermissionStatusAndAppearance();
} else {
UpdateText();
}
mojo::PendingRemote<EmbeddedPermissionControlClient> client;
embedded_permission_control_receiver_.Bind(
client.InitWithNewPipeAndPassReceiver(), GetTaskRunner());
@ -533,6 +549,11 @@ void HTMLPermissionElement::RemovedFrom(ContainerNode& insertion_point) {
if (auto* view = GetDocument().View()) {
view->UnregisterFromLifecycleNotifications(this);
}
if (LocalDOMWindow* window = GetDocument().domWindow()) {
CachedPermissionStatus::From(window)->UnregisterClient(
this, permission_descriptors_);
}
}
void HTMLPermissionElement::Focus(const FocusParams& params) {
@ -684,23 +705,16 @@ void HTMLPermissionElement::AttributeChanged(
type_ = params.new_value;
CHECK(permission_descriptors_.empty());
permission_descriptors_ = ParsePermissionDescriptorsFromString(GetType());
switch (permission_descriptors_.size()) {
case 0:
AddConsoleError(
String::Format("The permission type '%s' is not supported by the "
"permission element.",
GetType().Utf8().c_str()));
break;
case 1:
case 2:
UpdateText();
break;
default:
NOTREACHED_IN_MIGRATION()
<< "Unexpected permissions size " << permission_descriptors_.size();
if (permission_descriptors_.empty()) {
AddConsoleError("The permission type '" + GetType().GetString() +
"' is not supported by the "
"permission element.");
return;
}
CHECK_LE(permission_descriptors_.size(), 2U)
<< "Unexpected permissions size " << permission_descriptors_.size();
}
if (params.name == html_names::kPreciselocationAttr) {
@ -717,6 +731,10 @@ void HTMLPermissionElement::AttributeChanged(
UpdateText();
}
if (!permission_status_map_.empty()) {
UpdatePermissionStatusAndAppearance();
}
HTMLElement::AttributeChanged(params);
}
@ -972,8 +990,7 @@ void HTMLPermissionElement::OnPermissionStatusChange(
CHECK(it != permission_status_map_.end());
it->value = status;
PermissionStatusUpdated();
UpdateAppearance();
UpdatePermissionStatusAndAppearance();
}
void HTMLPermissionElement::OnEmbeddedPermissionControlRegistered(
@ -1003,8 +1020,7 @@ void HTMLPermissionElement::OnEmbeddedPermissionControlRegistered(
}
}
PermissionStatusUpdated();
UpdateAppearance();
UpdatePermissionStatusAndAppearance();
MaybeDispatchValidationChangeEvent();
}
@ -1262,7 +1278,24 @@ void HTMLPermissionElement::RefreshDisableReasonsAndUpdateTimer() {
MaybeDispatchValidationChangeEvent();
}
void HTMLPermissionElement::UpdateAppearance() {
void HTMLPermissionElement::UpdatePermissionStatusAndAppearance() {
if (base::ranges::any_of(permission_status_map_, [](const auto& status) {
return status.value == MojoPermissionStatus::DENIED;
})) {
aggregated_permission_status_ = MojoPermissionStatus::DENIED;
} else if (base::ranges::any_of(
permission_status_map_, [](const auto& status) {
return status.value == MojoPermissionStatus::ASK;
})) {
aggregated_permission_status_ = MojoPermissionStatus::ASK;
} else {
aggregated_permission_status_ = MojoPermissionStatus::GRANTED;
}
if (!initial_aggregated_permission_status_.has_value()) {
initial_aggregated_permission_status_ = aggregated_permission_status_;
}
PseudoStateChanged(CSSSelector::kPseudoPermissionGranted);
UpdateText();
}
@ -1579,23 +1612,4 @@ HTMLPermissionElement::GetRecentlyAttachedTimeoutRemaining() const {
return it->value - now;
}
void HTMLPermissionElement::PermissionStatusUpdated() {
if (base::ranges::any_of(permission_status_map_, [](const auto& status) {
return status.value == MojoPermissionStatus::DENIED;
})) {
aggregated_permission_status_ = MojoPermissionStatus::DENIED;
} else if (base::ranges::any_of(
permission_status_map_, [](const auto& status) {
return status.value == MojoPermissionStatus::ASK;
})) {
aggregated_permission_status_ = MojoPermissionStatus::ASK;
} else {
aggregated_permission_status_ = MojoPermissionStatus::GRANTED;
}
if (!initial_aggregated_permission_status_.has_value()) {
initial_aggregated_permission_status_ = aggregated_permission_status_;
}
}
} // namespace blink

@ -15,6 +15,7 @@
#include "third_party/blink/renderer/core/css/properties/css_property.h"
#include "third_party/blink/renderer/core/css/resolver/cascade_filter.h"
#include "third_party/blink/renderer/core/dom/events/event_target.h"
#include "third_party/blink/renderer/core/frame/cached_permission_status.h"
#include "third_party/blink/renderer/core/frame/local_frame_view.h"
#include "third_party/blink/renderer/core/html/html_element.h"
#include "third_party/blink/renderer/core/intersection_observer/intersection_observer.h"
@ -37,7 +38,8 @@ class CORE_EXPORT HTMLPermissionElement final
public mojom::blink::PermissionObserver,
public mojom::blink::EmbeddedPermissionControlClient,
public ScrollSnapshotClient,
public LocalFrameView::LifecycleNotificationObserver {
public LocalFrameView::LifecycleNotificationObserver,
public CachedPermissionStatus::Client {
DEFINE_WRAPPERTYPEINFO();
public:
@ -58,6 +60,11 @@ class CORE_EXPORT HTMLPermissionElement final
void Trace(Visitor*) const override;
// CachedPermissionStatus::Client overrides.
void OnPermissionStatusInitialized(
mojom::blink::PermissionName permission,
mojom::blink::PermissionStatus status) override;
InsertionNotificationRequest InsertedInto(ContainerNode&) override;
void RemovedFrom(ContainerNode&) override;
void AttachLayoutTree(AttachContext& context) override;
@ -351,7 +358,7 @@ class CORE_EXPORT HTMLPermissionElement final
// populated only *after* the permission element has been registered in
// browser process.
bool IsRegisteredInBrowserProcess() const {
return !permission_status_map_.empty();
return !permission_observer_receivers_.empty();
}
scoped_refptr<base::SingleThreadTaskRunner> GetTaskRunner();
@ -410,7 +417,11 @@ class CORE_EXPORT HTMLPermissionElement final
// alive temporary disabling reason".
void RefreshDisableReasonsAndUpdateTimer();
void UpdateAppearance();
// Called when the |permission_status_map_| is updated to
// - Ensure that |aggregated_permission_status_| and
// |initial_aggregated_permission_status_| are updated.
// - Update appearance based on the current statuses.
void UpdatePermissionStatusAndAppearance();
void UpdateText();
@ -457,11 +468,6 @@ class CORE_EXPORT HTMLPermissionElement final
it->value == base::TimeTicks::Max();
}
// Called when the |permission_status_map_| is updated to ensure that
// |aggregated_permission_status_| and |initial_aggregated_permission_status_|
// are updated.
void PermissionStatusUpdated();
bool PermissionsGranted() const {
return aggregated_permission_status_.has_value() &&
aggregated_permission_status_ ==

@ -536,6 +536,13 @@ class DeferredChecker {
};
TEST_F(HTMLPemissionElementTest, InitializeInnerText) {
CachedPermissionStatus::From(GetDocument().domWindow())
->SetPermissionStatusMap({{blink::mojom::PermissionName::VIDEO_CAPTURE,
MojoPermissionStatus::ASK},
{blink::mojom::PermissionName::AUDIO_CAPTURE,
MojoPermissionStatus::ASK},
{blink::mojom::PermissionName::GEOLOCATION,
MojoPermissionStatus::ASK}});
const struct {
const char* type;
String expected_text;
@ -555,13 +562,13 @@ TEST_F(HTMLPemissionElementTest, InitializeInnerText) {
permission_element->setAttribute(html_names::kPreciselocationAttr,
AtomicString(""));
}
EXPECT_EQ(
data.expected_text,
permission_element->permission_text_span_for_testing()->innerText());
permission_element->setAttribute(html_names::kStyleAttr,
AtomicString("width: auto; height: auto"));
GetDocument().body()->AppendChild(permission_element);
GetDocument().UpdateStyleAndLayout(DocumentUpdateReason::kTest);
EXPECT_EQ(
data.expected_text,
permission_element->permission_text_span_for_testing()->innerText());
DOMRect* rect = permission_element->GetBoundingClientRect();
EXPECT_NE(0, rect->width());
EXPECT_NE(0, rect->height());
@ -780,10 +787,13 @@ TEST_F(HTMLPemissionElementTest,
}
}
TEST_F(HTMLPemissionElementTest, InitialAndUpdatedPermissionStatusMicCamera) {
TEST_F(HTMLPemissionElementTest, InitialAndUpdatedPermissionStatus) {
for (const auto initial_status :
{MojoPermissionStatus::ASK, MojoPermissionStatus::DENIED,
MojoPermissionStatus::GRANTED}) {
CachedPermissionStatus::From(GetDocument().domWindow())
->SetPermissionStatusMap(
{{blink::mojom::PermissionName::GEOLOCATION, initial_status}});
V8PermissionState::Enum expected_initial_status =
PermissionStatusV8Enum(initial_status);
auto* permission_element = CreatePermissionElement("geolocation");
@ -810,7 +820,12 @@ TEST_F(HTMLPemissionElementTest, InitialAndUpdatedPermissionStatusMicCamera) {
}
}
TEST_F(HTMLPemissionElementTest, InitialAndUpdatedPermissionStatusCameraMic) {
TEST_F(HTMLPemissionElementTest, InitialAndUpdatedPermissionStatusGrouped) {
CachedPermissionStatus::From(GetDocument().domWindow())
->SetPermissionStatusMap({{blink::mojom::PermissionName::VIDEO_CAPTURE,
MojoPermissionStatus::ASK},
{blink::mojom::PermissionName::AUDIO_CAPTURE,
MojoPermissionStatus::ASK}});
auto* permission_element = CreatePermissionElement("camera microphone");
permission_service()->set_initial_statuses(
{MojoPermissionStatus::ASK, MojoPermissionStatus::DENIED});
@ -829,7 +844,7 @@ TEST_F(HTMLPemissionElementTest, InitialAndUpdatedPermissionStatusCameraMic) {
// The status is the most restrictive of the two permissions. The initial
// status never changes. camera: ASK, mic: DENIED
EXPECT_EQ(PermissionStatusV8Enum(MojoPermissionStatus::DENIED),
EXPECT_EQ(PermissionStatusV8Enum(MojoPermissionStatus::ASK),
permission_element->initialPermissionStatus());
EXPECT_EQ(PermissionStatusV8Enum(MojoPermissionStatus::DENIED),
permission_element->permissionStatus());
@ -837,7 +852,7 @@ TEST_F(HTMLPemissionElementTest, InitialAndUpdatedPermissionStatusCameraMic) {
// camera:ASK, mic: ASK
permission_service()->NotifyPermissionStatusChange(
PermissionName::AUDIO_CAPTURE, MojoPermissionStatus::ASK);
EXPECT_EQ(PermissionStatusV8Enum(MojoPermissionStatus::DENIED),
EXPECT_EQ(PermissionStatusV8Enum(MojoPermissionStatus::ASK),
permission_element->initialPermissionStatus());
EXPECT_EQ(PermissionStatusV8Enum(MojoPermissionStatus::ASK),
permission_element->permissionStatus());
@ -845,7 +860,7 @@ TEST_F(HTMLPemissionElementTest, InitialAndUpdatedPermissionStatusCameraMic) {
// camera:DENIED, mic: ASK
permission_service()->NotifyPermissionStatusChange(
PermissionName::VIDEO_CAPTURE, MojoPermissionStatus::DENIED);
EXPECT_EQ(PermissionStatusV8Enum(MojoPermissionStatus::DENIED),
EXPECT_EQ(PermissionStatusV8Enum(MojoPermissionStatus::ASK),
permission_element->initialPermissionStatus());
EXPECT_EQ(PermissionStatusV8Enum(MojoPermissionStatus::DENIED),
permission_element->permissionStatus());
@ -853,7 +868,7 @@ TEST_F(HTMLPemissionElementTest, InitialAndUpdatedPermissionStatusCameraMic) {
// camera:DENIED, mic: GRANTED
permission_service()->NotifyPermissionStatusChange(
PermissionName::AUDIO_CAPTURE, MojoPermissionStatus::GRANTED);
EXPECT_EQ(PermissionStatusV8Enum(MojoPermissionStatus::DENIED),
EXPECT_EQ(PermissionStatusV8Enum(MojoPermissionStatus::ASK),
permission_element->initialPermissionStatus());
EXPECT_EQ(PermissionStatusV8Enum(MojoPermissionStatus::DENIED),
permission_element->permissionStatus());
@ -861,7 +876,7 @@ TEST_F(HTMLPemissionElementTest, InitialAndUpdatedPermissionStatusCameraMic) {
// camera:GRANTED, mic: GRANTED
permission_service()->NotifyPermissionStatusChange(
PermissionName::VIDEO_CAPTURE, MojoPermissionStatus::GRANTED);
EXPECT_EQ(PermissionStatusV8Enum(MojoPermissionStatus::DENIED),
EXPECT_EQ(PermissionStatusV8Enum(MojoPermissionStatus::ASK),
permission_element->initialPermissionStatus());
EXPECT_EQ(PermissionStatusV8Enum(MojoPermissionStatus::GRANTED),
permission_element->permissionStatus());
@ -955,6 +970,46 @@ class HTMLPemissionElementSimTest : public SimTest {
ScopedPermissionElementForTest scoped_feature_{true};
};
TEST_F(HTMLPemissionElementSimTest, InitializeGrantedText) {
SimRequest resource("https://example.test", "text/html");
LoadURL("https://example.test");
resource.Complete(R"(
<body>
</body>
)");
CachedPermissionStatus::From(GetDocument().domWindow())
->SetPermissionStatusMap({{blink::mojom::PermissionName::VIDEO_CAPTURE,
MojoPermissionStatus::GRANTED},
{blink::mojom::PermissionName::AUDIO_CAPTURE,
MojoPermissionStatus::GRANTED},
{blink::mojom::PermissionName::GEOLOCATION,
MojoPermissionStatus::GRANTED}});
const struct {
const char* type;
String expected_text;
} kTestData[] = {{"geolocation", kGeolocationAllowedString},
{"microphone", kMicrophoneAllowedString},
{"camera", kCameraAllowedString},
{"camera microphone", kCameraMicrophoneAllowedString}};
for (const auto& data : kTestData) {
auto* permission_element =
MakeGarbageCollected<HTMLPermissionElement>(GetDocument());
permission_element->setAttribute(html_names::kTypeAttr,
AtomicString(data.type));
permission_element->setAttribute(html_names::kStyleAttr,
AtomicString("width: auto; height: auto"));
GetDocument().body()->AppendChild(permission_element);
GetDocument().UpdateStyleAndLayout(DocumentUpdateReason::kTest);
EXPECT_EQ(
data.expected_text,
permission_element->permission_text_span_for_testing()->innerText());
DOMRect* rect = permission_element->GetBoundingClientRect();
EXPECT_NE(0, rect->width());
EXPECT_NE(0, rect->height());
}
}
TEST_F(HTMLPemissionElementSimTest, BlockedByPermissionsPolicy) {
SimRequest main_resource("https://example.test", "text/html");
LoadURL("https://example.test");
@ -1472,7 +1527,10 @@ TEST_F(HTMLPemissionElementSimTest, GrantedSelectorDisplayNone) {
permission_element->GetComputedStyle()->GetDisplayStyle().Display());
}
TEST_F(HTMLPemissionElementSimTest, MovePEPCToAnotherDocument) {
// TODO(crbug.com/375231573): We should verify this test again. It's likely when
// moving PEPC between documents, the execution context binding to permission
// service will be changed.
TEST_F(HTMLPemissionElementSimTest, DISABLED_MovePEPCToAnotherDocument) {
SimRequest main_resource("https://example.test/", "text/html");
SimRequest iframe_resource("https://example.test/foo.html", "text/html");
LoadURL("https://example.test/");

@ -91,6 +91,7 @@
#include "third_party/blink/renderer/core/execution_context/window_agent.h"
#include "third_party/blink/renderer/core/execution_context/window_agent_factory.h"
#include "third_party/blink/renderer/core/fragment_directive/text_fragment_anchor.h"
#include "third_party/blink/renderer/core/frame/cached_permission_status.h"
#include "third_party/blink/renderer/core/frame/csp/content_security_policy.h"
#include "third_party/blink/renderer/core/frame/deprecation/deprecation.h"
#include "third_party/blink/renderer/core/frame/frame_console.h"
@ -352,6 +353,9 @@ struct SameSizeAsDocumentLoader
AtomicString cookie_deprecation_label;
mojom::RendererContentSettingsPtr content_settings;
int64_t body_size_from_service_worker;
const std::optional<
HashMap<mojom::blink::PermissionName, mojom::blink::PermissionStatus>>
initial_permission_statuses;
};
// Asserts size of DocumentLoader, so that whenever a new attribute is added to
@ -418,6 +422,37 @@ bool ShouldEmitNewNavigationHistogram(WebNavigationType navigation_type) {
}
}
// Helpers to convert between base::flat_map and WTF::HashMap
std::optional<
HashMap<mojom::blink::PermissionName, mojom::blink::PermissionStatus>>
ConvertPermissionStatusFlatMapToHashMap(
const std::optional<base::flat_map<mojom::blink::PermissionName,
mojom::blink::PermissionStatus>>&
flat_map) {
if (!flat_map) {
return std::nullopt;
}
HashMap<mojom::blink::PermissionName, mojom::blink::PermissionStatus>
hash_map;
for (const auto& it : *flat_map) {
hash_map.insert(it.first, it.second);
}
return hash_map;
}
base::flat_map<mojom::blink::PermissionName, mojom::blink::PermissionStatus>
ConvertPermissionStatusHashMapToFlatMap(
const HashMap<mojom::blink::PermissionName, mojom::blink::PermissionStatus>&
hash_map) {
base::flat_map<mojom::blink::PermissionName, mojom::blink::PermissionStatus>
flat_map;
for (const auto& it : hash_map) {
flat_map.try_emplace(it.key, it.value);
}
return flat_map;
}
} // namespace
// Base class for body data received by the loader. This allows abstracting away
@ -573,7 +608,9 @@ DocumentLoader::DocumentLoader(
browsing_context_group_info_(params_->browsing_context_group_info),
modified_runtime_features_(std::move(params_->modified_runtime_features)),
cookie_deprecation_label_(params_->cookie_deprecation_label),
content_settings_(std::move(params_->content_settings)) {
content_settings_(std::move(params_->content_settings)),
initial_permission_statuses_(ConvertPermissionStatusFlatMapToHashMap(
params_->initial_permission_statuses)) {
TRACE_EVENT_WITH_FLOW0("loading", "DocumentLoader::DocumentLoader",
TRACE_ID_LOCAL(this), TRACE_EVENT_FLAG_FLOW_OUT);
DCHECK(frame_);
@ -731,6 +768,14 @@ DocumentLoader::CreateWebNavigationParamsToCloneDocument() {
params->cookie_deprecation_label = cookie_deprecation_label_;
params->visited_link_salt = visited_link_salt_;
params->content_settings = content_settings_->Clone();
if (RuntimeEnabledFeatures::PermissionElementEnabled(
frame_->DomWindow()->GetExecutionContext())) {
params->initial_permission_statuses =
ConvertPermissionStatusHashMapToFlatMap(
CachedPermissionStatus::From(frame_->DomWindow())
->GetPermissionStatusMap());
}
return params;
}
@ -2702,6 +2747,15 @@ void DocumentLoader::InitializeWindow(Document* owner_document) {
// above.
DCHECK(did_have_policy_container || WillLoadUrlAsEmpty(Url()));
}
if (initial_permission_statuses_ &&
RuntimeEnabledFeatures::PermissionElementEnabled(
frame_->DomWindow()->GetExecutionContext())) {
CachedPermissionStatus::From(frame_->DomWindow())
->SetPermissionStatusMap(
std::move(initial_permission_statuses_).value());
}
content_security_notifier_ =
HeapMojoRemote<mojom::blink::ContentSecurityNotifier>(
frame_->DomWindow());

@ -57,6 +57,7 @@
#include "third_party/blink/public/mojom/navigation/navigation_params.mojom-shared.h"
#include "third_party/blink/public/mojom/page/page.mojom-blink-forward.h"
#include "third_party/blink/public/mojom/page_state/page_state.mojom-blink.h"
#include "third_party/blink/public/mojom/permissions/permission.mojom-blink.h"
#include "third_party/blink/public/mojom/runtime_feature_state/runtime_feature.mojom-blink-forward.h"
#include "third_party/blink/public/mojom/service_worker/controller_service_worker_mode.mojom-blink.h"
#include "third_party/blink/public/mojom/timing/resource_timing.mojom-blink-forward.h"
@ -883,6 +884,14 @@ class CORE_EXPORT DocumentLoader : public GarbageCollected<DocumentLoader>,
// When document is fetched from service worker, we keep track of the body
// size for reporting in Navigation Timing encodedBodySize/decodedBodySize.
int64_t total_body_size_from_service_worker_ = 0;
// Map of permission statuses, snapshotting and propagated from browser before
// committing a navigation.
// Note: the permission statues will be only used as initial states of
// `CachedPermissionStatus`
std::optional<
HashMap<mojom::blink::PermissionName, mojom::blink::PermissionStatus>>
initial_permission_statuses_;
};
DECLARE_WEAK_IDENTIFIER_MAP(DocumentLoader);

@ -3236,7 +3236,7 @@
name: "PermissionElement",
origin_trial_feature_name: "PermissionElement",
origin_trial_os: ["win", "mac", "linux", "fuchsia", "chromeos"],
status: "experimental",
status: {"Android": "", "default": "experimental"},
public: true,
base_feature_status: "enabled",
copied_from_base_feature_if: "overridden",