[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:

committed by
Chromium LUCI CQ

parent
34981def10
commit
1924a27af3
components/performance_manager
content
browser
permissions
renderer_host
public
renderer
third_party/blink
public
renderer
@ -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",
|
||||
|
130
third_party/blink/renderer/core/frame/cached_permission_status.cc
vendored
Normal file
130
third_party/blink/renderer/core/frame/cached_permission_status.cc
vendored
Normal file
@ -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
|
149
third_party/blink/renderer/core/frame/cached_permission_status.h
vendored
Normal file
149
third_party/blink/renderer/core/frame/cached_permission_status.h
vendored
Normal file
@ -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_
|
182
third_party/blink/renderer/core/frame/cached_permission_status_test.cc
vendored
Normal file
182
third_party/blink/renderer/core/frame/cached_permission_status_test.cc
vendored
Normal file
@ -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",
|
||||
|
Reference in New Issue
Block a user