[PEPC] Track instances per page by visibility
We'll keep track of PEPC instances based on their rendered states.
Registration and unregistration will be checked when elements are
inserted to/removed from the page and updated at the end of lifecycle
phases.
https://docs.google.com/document/d/1c8JZAi9t-_ZPbYXIfUzgJmsfMRLDTxgHg15GglmGB4Q/edit?resourcekey=0-rrRb6K_42FfuDC8r9mlO9A&tab=t.0
Bug: 407938235
Change-Id: Ibb725d3d408e312c0fcb60cef724cd6754d62bc6
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/6512933
Commit-Queue: Thomas Nguyen <tungnh@chromium.org>
Reviewed-by: Mason Freed <masonf@chromium.org>
Reviewed-by: Rune Lillesveen <futhark@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1461285}
diff --git a/third_party/blink/renderer/core/html/html_permission_element.cc b/third_party/blink/renderer/core/html/html_permission_element.cc
index 1429ac6..1fb09b8 100644
--- a/third_party/blink/renderer/core/html/html_permission_element.cc
+++ b/third_party/blink/renderer/core/html/html_permission_element.cc
@@ -417,21 +417,6 @@
DCHECK(RuntimeEnabledFeatures::PermissionElementEnabled(
document.GetExecutionContext()));
SetHasCustomStyleCallbacks();
- intersection_observer_ = IntersectionObserver::Create(
- GetDocument(),
- WTF::BindRepeating(&HTMLPermissionElement::OnIntersectionChanged,
- WrapWeakPersistent(this)),
- LocalFrameUkmAggregator::kPermissionElementIntersectionObserver,
- IntersectionObserver::Params{
- .thresholds = {kIntersectionThreshold},
- .semantics = IntersectionObserver::kFractionOfTarget,
- .behavior = IntersectionObserver::kDeliverDuringPostLifecycleSteps,
- .delay = base::Milliseconds(100),
- .track_visibility = true,
- .expose_occluder_id = true,
- });
-
- intersection_observer_->observe(this);
EnsureUserAgentShadowRoot();
UseCounter::Count(document, WebFeature::kHTMLPermissionElement);
}
@@ -479,16 +464,39 @@
Node::InsertionNotificationRequest HTMLPermissionElement::InsertedInto(
ContainerNode& insertion_point) {
HTMLElement::InsertedInto(insertion_point);
- MaybeRegisterPageEmbeddedPermissionControl();
+ if (!permission_descriptors_.empty()) {
+ CachedPermissionStatus::From(GetDocument().domWindow())
+ ->RegisterClient(this, permission_descriptors_);
+ }
return kInsertionDone;
}
void HTMLPermissionElement::AttachLayoutTree(AttachContext& context) {
Element::AttachLayoutTree(context);
+ if (fallback_mode_) {
+ return;
+ }
DisableClickingTemporarily(DisableReason::kRecentlyAttachedToLayoutTree,
kDefaultDisableTimeout);
CHECK(GetDocument().View());
GetDocument().View()->RegisterForLifecycleNotifications(this);
+ if (!intersection_observer_) {
+ intersection_observer_ = IntersectionObserver::Create(
+ GetDocument(),
+ WTF::BindRepeating(&HTMLPermissionElement::OnIntersectionChanged,
+ WrapWeakPersistent(this)),
+ LocalFrameUkmAggregator::kPermissionElementIntersectionObserver,
+ IntersectionObserver::Params{
+ .thresholds = {kIntersectionThreshold},
+ .semantics = IntersectionObserver::kFractionOfTarget,
+ .behavior = IntersectionObserver::kDeliverDuringPostLifecycleSteps,
+ .delay = base::Milliseconds(100),
+ .track_visibility = true,
+ .expose_occluder_id = true,
+ });
+
+ intersection_observer_->observe(this);
+ }
}
void HTMLPermissionElement::DetachLayoutTree(bool performing_reattach) {
@@ -507,15 +515,11 @@
disable_reason_expire_timer_.Stop();
}
intersection_rect_ = std::nullopt;
- if (embedded_permission_control_receiver_.is_bound()) {
- embedded_permission_control_receiver_.reset();
- }
-
- is_registered_in_browser_process_ = false;
if (LocalDOMWindow* window = GetDocument().domWindow()) {
CachedPermissionStatus::From(window)->UnregisterClient(
this, permission_descriptors_);
}
+ EnsureUnregisterPageEmbeddedPermissionControl();
}
void HTMLPermissionElement::Focus(const FocusParams& params) {
@@ -572,6 +576,14 @@
DisableReason::kIntersectionVisibilityOccludedOrDistorted);
}
+bool HTMLPermissionElement::IsRenderered() const {
+ LayoutObject* layout_object = GetLayoutObject();
+ if (!layout_object) {
+ return false;
+ }
+ return layout_object->StyleRef().Visibility() == EVisibility::kVisible;
+}
+
// static
Vector<PermissionDescriptorPtr>
HTMLPermissionElement::ParsePermissionDescriptorsForTesting(
@@ -697,16 +709,27 @@
}
}
- CachedPermissionStatus::From(GetDocument().domWindow())
- ->RegisterClient(this, permission_descriptors_);
+ if (!IsRenderered()) {
+ return false;
+ }
+
mojo::PendingRemote<EmbeddedPermissionControlClient> client;
embedded_permission_control_receiver_.Bind(
client.InitWithNewPipeAndPassReceiver(), GetTaskRunner());
+ CHECK(embedded_permission_control_receiver_.is_bound());
GetPermissionService()->RegisterPageEmbeddedPermissionControl(
mojo::Clone(permission_descriptors_), std::move(client));
return true;
}
+void HTMLPermissionElement::EnsureUnregisterPageEmbeddedPermissionControl() {
+ if (embedded_permission_control_receiver_.is_bound()) {
+ embedded_permission_control_receiver_.reset();
+ }
+
+ is_registered_in_browser_process_ = false;
+}
+
void HTMLPermissionElement::LangAttributeChanged() {
UpdateText();
HTMLElement::LangAttributeChanged();
@@ -1164,6 +1187,12 @@
return false;
}
+ // Do not check click-disabling reasons if the PEPC validation feature is
+ // disabled. This should only occur in testing scenarios.
+ if (RuntimeEnabledFeatures::BypassPepcSecurityForTestingEnabled()) {
+ return true;
+ }
+
if (!is_registered_in_browser_process()) {
AddConsoleError(
WTF::StrCat({"The permission element '", GetType(),
@@ -1175,12 +1204,6 @@
return false;
}
- // Do not check click-disabling reasons if the PEPC validation feature is
- // disabled. This should only occur in testing scenarios.
- if (RuntimeEnabledFeatures::BypassPepcSecurityForTestingEnabled()) {
- return true;
- }
-
// Remove expired reasons. If the remaining map is not empty, clicking is
// disabled. Record and log all the remaining reasons in the map in this case.
base::TimeTicks now = base::TimeTicks::Now();
@@ -1645,6 +1668,12 @@
kDefaultDisableTimeout);
}
intersection_rect_ = intersection_rect;
+
+ if (IsRenderered()) {
+ MaybeRegisterPageEmbeddedPermissionControl();
+ } else {
+ EnsureUnregisterPageEmbeddedPermissionControl();
+ }
}
gfx::Rect HTMLPermissionElement::ComputeIntersectionRectWithViewport(
@@ -1678,8 +1707,9 @@
void HTMLPermissionElement::EnableFallbackMode() {
CHECK(!fallback_mode_);
fallback_mode_ = true;
- intersection_observer_->unobserve(this);
-
+ if (intersection_observer_) {
+ intersection_observer_->unobserve(this);
+ }
// Adding this slot element will make all children of the permission element
// render, the permission element's built-in elements are removed at the same
// time.
diff --git a/third_party/blink/renderer/core/html/html_permission_element.h b/third_party/blink/renderer/core/html/html_permission_element.h
index 3b03e04e..c51c3ba 100644
--- a/third_party/blink/renderer/core/html/html_permission_element.h
+++ b/third_party/blink/renderer/core/html/html_permission_element.h
@@ -86,6 +86,7 @@
bool HasInvalidStyle() const;
bool IsOccluded() const;
+ bool IsRenderered() const;
bool granted() const { return PermissionsGranted(); }
// Given an input type, return permissions list. This method is for testing
@@ -136,6 +137,8 @@
FontSizeCanDisableElement);
FRIEND_TEST_ALL_PREFIXES(HTMLPermissionElementSimTest,
MovePEPCToAnotherDocument);
+ FRIEND_TEST_ALL_PREFIXES(HTMLPermissionElementSimTest,
+ RegisterAfterBeingVisible);
FRIEND_TEST_ALL_PREFIXES(HTMLPermissionElementLayoutChangeTest,
InvalidatePEPCAfterMove);
FRIEND_TEST_ALL_PREFIXES(HTMLPermissionElementLayoutChangeTest,
@@ -309,6 +312,9 @@
// process.
bool MaybeRegisterPageEmbeddedPermissionControl();
+ // Ensure we reset the PEPC IPC endpoint.
+ void EnsureUnregisterPageEmbeddedPermissionControl();
+
// blink::Element implements
void AttributeChanged(const AttributeModificationParams& params) override;
void DidAddUserAgentShadowRoot(ShadowRoot&) override;
diff --git a/third_party/blink/renderer/core/html/html_permission_element_test.cc b/third_party/blink/renderer/core/html/html_permission_element_test.cc
index d8af608f..3513ea3b 100644
--- a/third_party/blink/renderer/core/html/html_permission_element_test.cc
+++ b/third_party/blink/renderer/core/html/html_permission_element_test.cc
@@ -450,6 +450,7 @@
}
GetDocument().body()->AppendChild(permission_element);
GetDocument().UpdateStyleAndLayout(DocumentUpdateReason::kTest);
+ GetDocument().View()->UpdateAllLifecyclePhasesForTest();
return permission_element;
}
@@ -559,6 +560,7 @@
AtomicString("width: auto; height: auto"));
GetDocument().body()->AppendChild(permission_element);
GetDocument().UpdateStyleAndLayout(DocumentUpdateReason::kTest);
+ GetDocument().View()->UpdateAllLifecyclePhasesForTest();
EXPECT_EQ(
data.expected_text,
permission_element->permission_text_span_for_testing()->innerText());
@@ -598,6 +600,7 @@
permission_service()->NotifyPermissionStatusChange(
PermissionName::GEOLOCATION, MojoPermissionStatus::ASK);
GetDocument().UpdateStyleAndLayout(DocumentUpdateReason::kTest);
+ GetDocument().View()->UpdateAllLifecyclePhasesForTest();
EXPECT_EQ(
data.expected_text_ask,
permission_element->permission_text_span_for_testing()->innerText());
@@ -605,6 +608,7 @@
permission_service()->NotifyPermissionStatusChange(
PermissionName::GEOLOCATION, MojoPermissionStatus::GRANTED);
GetDocument().UpdateStyleAndLayout(DocumentUpdateReason::kTest);
+ GetDocument().View()->UpdateAllLifecyclePhasesForTest();
EXPECT_EQ(
data.expected_text_allowed,
permission_element->permission_text_span_for_testing()->innerText());
@@ -661,6 +665,7 @@
permission_element->setAttribute(html_names::kPreciselocationAttr,
AtomicString(""));
}
+ UpdateAllLifecyclePhasesForTest();
RegistrationWaiter(permission_element).Wait();
EXPECT_EQ(
data.expected_text,
@@ -989,6 +994,7 @@
}
document.body()->AppendChild(permission_element);
document.UpdateStyleAndLayout(DocumentUpdateReason::kTest);
+ GetDocument().View()->UpdateAllLifecyclePhasesForTest();
return permission_element;
}
@@ -1081,7 +1087,6 @@
static_cast<frame_test_helpers::TestWebFrameClient*>(
first_child_frame->Client())
->ConsoleMessages();
- EXPECT_EQ(first_console_messages.size(), 2u);
EXPECT_TRUE(first_console_messages.front().Contains(
"is not allowed in the current context due to PermissionsPolicy"));
first_console_messages.clear();
@@ -1214,6 +1219,36 @@
}
}
+TEST_F(HTMLPermissionElementSimTest, RegisterAfterBeingVisible) {
+ SimRequest main_resource("https://example.test/", "text/html");
+ LoadURL("https://example.test/");
+ main_resource.Complete(R"HTML(
+ <body>
+ <permission
+ style='display:block; visibility:hidden'
+ id='camera'></permission>
+ </body>
+ )HTML");
+
+ Compositor().BeginFrame();
+ auto* permission_element = To<HTMLPermissionElement>(
+ GetDocument().QuerySelector(AtomicString("permission")));
+ GetDocument().View()->UpdateAllLifecyclePhasesForTest();
+ EXPECT_FALSE(permission_element->is_registered_in_browser_process());
+ permission_element->setAttribute(html_names::kTypeAttr,
+ AtomicString("camera"));
+ GetDocument().View()->UpdateAllLifecyclePhasesForTest();
+ EXPECT_FALSE(permission_element->is_registered_in_browser_process());
+ permission_element->setAttribute(html_names::kStyleAttr,
+ AtomicString("visibility:visible;"));
+ GetDocument().View()->UpdateAllLifecyclePhasesForTest();
+ RegistrationWaiter(permission_element).Wait();
+ permission_element->setAttribute(html_names::kStyleAttr,
+ AtomicString("display: none"));
+ GetDocument().View()->UpdateAllLifecyclePhasesForTest();
+ EXPECT_FALSE(permission_element->is_registered_in_browser_process());
+}
+
class HTMLPermissionElementDispatchValidationEventTest
: public HTMLPermissionElementSimTest {
public:
@@ -1234,6 +1269,7 @@
/*should_defer*/ true);
document.body()->AppendChild(permission_element);
document.UpdateStyleAndLayout(DocumentUpdateReason::kTest);
+ GetDocument().View()->UpdateAllLifecyclePhasesForTest();
DeferredChecker checker(permission_element, &MainFrame());
checker.CheckConsoleMessageAtIndex(0u, kValidationStatusChangeEvent);
EXPECT_FALSE(permission_element->isValid());
@@ -1245,6 +1281,8 @@
RegistrationWaiter(permission_element).Wait();
permission_service()->set_should_defer_registered_callback(
/*should_defer*/ false);
+ checker.CheckConsoleMessageAtIndex(1u, kValidationStatusChangeEvent);
+ ConsoleMessages().clear();
return permission_element;
}
@@ -1255,9 +1293,8 @@
// Test receiving event after registration
TEST_F(HTMLPermissionElementDispatchValidationEventTest, Registration) {
auto* permission_element = CreateElementAndWaitForRegistration();
- DeferredChecker checker(permission_element, &MainFrame());
- checker.CheckConsoleMessageAtIndex(1u, kValidationStatusChangeEvent);
- EXPECT_TRUE(permission_element->isValid());
+ EXPECT_TRUE(
+ base::test::RunUntil([&]() { return permission_element->isValid(); }));
}
// Test receiving event after several times disabling (temporarily or
@@ -1284,10 +1321,9 @@
for (const auto& data : kTestData) {
auto* permission_element = CreateElementAndWaitForRegistration();
DeferredChecker checker(permission_element, &MainFrame());
- checker.CheckConsoleMessageAtIndex(1u, kValidationStatusChangeEvent);
EXPECT_TRUE(permission_element->isValid());
permission_element->DisableClickingIndefinitely(data.reason);
- checker.CheckConsoleMessageAtIndex(2u, kValidationStatusChangeEvent);
+ checker.CheckConsoleMessageAtIndex(0u, kValidationStatusChangeEvent);
EXPECT_FALSE(permission_element->isValid());
EXPECT_EQ(permission_element->invalidReason(),
data.expected_invalid_reason);
@@ -1305,7 +1341,7 @@
EXPECT_FALSE(permission_element->isValid());
EXPECT_EQ(permission_element->invalidReason(),
data.expected_invalid_reason);
- checker.CheckConsoleMessageAtIndex(3u, kValidationStatusChangeEvent);
+ checker.CheckConsoleMessageAtIndex(1u, kValidationStatusChangeEvent);
EXPECT_TRUE(permission_element->isValid());
// Calling |EnableClickingAfterDelay| for a reason that is currently *not*
// disabling clicking does not do anything.
@@ -1313,11 +1349,11 @@
checker.CheckNoNewMessagesAfterDelay(kSmallTimeout);
permission_element->DisableClickingTemporarily(data.reason, kSmallTimeout);
- checker.CheckConsoleMessageAtIndex(4u, kValidationStatusChangeEvent);
+ checker.CheckConsoleMessageAtIndex(2u, kValidationStatusChangeEvent);
EXPECT_FALSE(permission_element->isValid());
EXPECT_EQ(permission_element->invalidReason(),
data.expected_invalid_reason);
- checker.CheckConsoleMessageAtIndex(5u, kValidationStatusChangeEvent);
+ checker.CheckConsoleMessageAtIndex(3u, kValidationStatusChangeEvent);
EXPECT_TRUE(permission_element->isValid());
GetDocument().body()->RemoveChild(permission_element);
@@ -1332,19 +1368,18 @@
ChangeReasonRestartTimer) {
auto* permission_element = CreateElementAndWaitForRegistration();
DeferredChecker checker(permission_element, &MainFrame());
- checker.CheckConsoleMessageAtIndex(1u, kValidationStatusChangeEvent);
EXPECT_TRUE(permission_element->isValid());
permission_element->DisableClickingTemporarily(
HTMLPermissionElement::DisableReason::kRecentlyAttachedToLayoutTree,
kSmallTimeout);
- checker.CheckConsoleMessageAtIndex(2u, kValidationStatusChangeEvent);
+ checker.CheckConsoleMessageAtIndex(0u, kValidationStatusChangeEvent);
EXPECT_FALSE(permission_element->isValid());
EXPECT_EQ(permission_element->invalidReason(), "recently_attached");
permission_element->DisableClickingTemporarily(
HTMLPermissionElement::DisableReason::kInvalidStyle, kDefaultTimeout);
// Reason change to the "longest alive" reason, in this case is
// `kInvalidStyle`
- checker.CheckConsoleMessageAtIndex(3u, kValidationStatusChangeEvent);
+ checker.CheckConsoleMessageAtIndex(1u, kValidationStatusChangeEvent);
EXPECT_FALSE(permission_element->isValid());
EXPECT_EQ(permission_element->invalidReason(), "style_invalid");
permission_element->DisableClickingTemporarily(
@@ -1354,10 +1389,10 @@
EXPECT_EQ(permission_element->invalidReason(), "style_invalid");
permission_element->EnableClickingAfterDelay(
HTMLPermissionElement::DisableReason::kInvalidStyle, kSmallTimeout);
- checker.CheckConsoleMessageAtIndex(4u, kValidationStatusChangeEvent);
+ checker.CheckConsoleMessageAtIndex(2u, kValidationStatusChangeEvent);
EXPECT_FALSE(permission_element->isValid());
EXPECT_EQ(permission_element->invalidReason(), "recently_attached");
- checker.CheckConsoleMessageAtIndex(5u, kValidationStatusChangeEvent);
+ checker.CheckConsoleMessageAtIndex(3u, kValidationStatusChangeEvent);
EXPECT_TRUE(permission_element->isValid());
}
@@ -1367,13 +1402,12 @@
DisableEnableClickingDifferentReasons) {
auto* permission_element = CreateElementAndWaitForRegistration();
DeferredChecker checker(permission_element, &MainFrame());
- checker.CheckConsoleMessageAtIndex(1u, kValidationStatusChangeEvent);
EXPECT_TRUE(permission_element->isValid());
permission_element->DisableClickingTemporarily(
HTMLPermissionElement::DisableReason::
kIntersectionVisibilityOutOfViewPortOrClipped,
kDefaultTimeout);
- checker.CheckConsoleMessageAtIndex(2u, kValidationStatusChangeEvent);
+ checker.CheckConsoleMessageAtIndex(0u, kValidationStatusChangeEvent);
EXPECT_FALSE(permission_element->isValid());
EXPECT_EQ(permission_element->invalidReason(),
"intersection_out_of_viewport_or_clipped");
@@ -1383,7 +1417,7 @@
HTMLPermissionElement::DisableReason::kInvalidStyle);
// `invalidReason` change from temporary `intersection` to indefinitely
// `style`
- checker.CheckConsoleMessageAtIndex(3u, kValidationStatusChangeEvent);
+ checker.CheckConsoleMessageAtIndex(1u, kValidationStatusChangeEvent);
EXPECT_FALSE(permission_element->isValid());
EXPECT_EQ(permission_element->invalidReason(), "style_invalid");
checker.CheckNoNewMessagesAfterDelay(kDefaultTimeout);
@@ -1399,11 +1433,11 @@
permission_element->EnableClicking(
HTMLPermissionElement::DisableReason::kInvalidStyle);
// `invalidReason` change from `style` to temporary `intersection`
- checker.CheckConsoleMessageAtIndex(4u, kValidationStatusChangeEvent);
+ checker.CheckConsoleMessageAtIndex(2u, kValidationStatusChangeEvent);
EXPECT_FALSE(permission_element->isValid());
EXPECT_EQ(permission_element->invalidReason(),
"intersection_out_of_viewport_or_clipped");
- checker.CheckConsoleMessageAtIndex(5u, kValidationStatusChangeEvent);
+ checker.CheckConsoleMessageAtIndex(3u, kValidationStatusChangeEvent);
EXPECT_TRUE(permission_element->isValid());
}
@@ -1489,7 +1523,6 @@
static_cast<frame_test_helpers::TestWebFrameClient*>(
first_child_frame->Client())
->ConsoleMessages();
- EXPECT_EQ(first_console_messages.size(), 2u);
EXPECT_TRUE(first_console_messages.front().Contains(
"is not allowed without the CSP 'frame-ancestors' directive present."));
first_console_messages.clear();