Skip to content

Commit

Permalink
[Remote Inspection] Make it possible to target ::before/::after pseud…
Browse files Browse the repository at this point in the history
…o elements

https://bugs.webkit.org/show_bug.cgi?id=271791

Reviewed by Megan Gardner.

(...and also incorporates suggestions from Antti).

Add support for targeting `::before` and `::after` pseudo elements, for visibility adjustment. See
below for more details.

* LayoutTests/fast/element-targeting/target-pseudo-elements-expected.html: Added.
* LayoutTests/fast/element-targeting/target-pseudo-elements.html: Added.

Add a new layout test to exercise the change (targeting both `after` and `before` pseudo elements).

* Source/WebCore/Headers.cmake:

Add a new WebCore header that just contains the new `VisibilityAdjustment` enum values.

* Source/WebCore/WebCore.xcodeproj/project.pbxproj:
* Source/WebCore/dom/Element.cpp:
(WebCore::Element::visibilityAdjustment const):
(WebCore::Element::addVisibilityAdjustment):
(WebCore::Element::isVisibilityAdjustmentRoot const): Deleted.
(WebCore::Element::setIsVisibilityAdjustmentRoot): Deleted.

Refactor this code — instead of having a single boolean flag that indicates whether the element is
a visibility adjustment root, turn it into an OptionSet with up to 3 bits (subtree, `::before`
pseudo element and `::after` pseudo element).

* Source/WebCore/dom/Element.h:
* Source/WebCore/dom/ElementRareData.cpp:
* Source/WebCore/dom/ElementRareData.h:
(WebCore::ElementRareData::visibilityAdjustment const):
(WebCore::ElementRareData::addVisibilityAdjustment):
(WebCore::ElementRareData::isVisibilityAdjustmentRoot const): Deleted.
(WebCore::ElementRareData::setIsVisibilityAdjustmentRoot): Deleted.

See above for more details — replace the single `isVisibilityAdjustmentRoot` flag with a new
`OptionSet`.

* Source/WebCore/dom/VisibilityAdjustment.h: Copied from Source/WebCore/page/ElementTargetingTypes.h.
* Source/WebCore/page/ElementTargetingController.cpp:
(WebCore::selectorsForTarget):
(WebCore::targetedElementInfo):

Teach this to emit `::before` and `::after` selectors for pseudo elements.

(WebCore::isTargetCandidate):

Teach this helper function to always consider rendered `::before` and `::after` pseudo elements to
be candidates for target selection, since adjusting visibility for these elements is unlikely to
adversely affect "main content" on the page.

(WebCore::elementToAdjust):
(WebCore::adjustmentToApply):
(WebCore::adjustVisibilityIfNeeded):

Split some of this functionality into multiple helper methods, to make it easier to determine how
an element should be marked for visibility adjustment. In particular, for before and after pseudo
elements, we mark the pseudo host element with `VisibilityAdjustment::{Before|After}Pseudo`, and for
everything else, we mark the element subtree (i.e. the same as what we currently do).

(WebCore::ElementTargetingController::adjustVisibility):
(WebCore::ElementTargetingController::adjustVisibilityInRepeatedlyTargetedRegions):
* Source/WebCore/page/ElementTargetingTypes.h:
* Source/WebCore/style/StyleAdjuster.cpp:
(WebCore::Style::Adjuster::adjust const):
(WebCore::Style::Adjuster::adjustVisibilityForPseudoElement):

Honor `VisibilityAdjustment::{BeforePseudo|AfterPseudo}` on the host element, by marking the
corresponding pseudo element style with visibility adjustment.

* Source/WebCore/style/StyleAdjuster.h:
* Source/WebCore/style/StyleResolver.cpp:
(WebCore::Style::Resolver::styleForPseudoElement):
* Source/WebCore/style/StyleTreeResolver.cpp:
(WebCore::Style::TreeResolver::resolveStartingStyle const):
* Source/WebKit/Shared/WebCoreArgumentCoders.serialization.in:
* Source/WebKit/UIProcess/API/APITargetedElementInfo.h:
* Source/WebKit/UIProcess/API/Cocoa/_WKTargetedElementInfo.h:
* Source/WebKit/UIProcess/API/Cocoa/_WKTargetedElementInfo.mm:
(-[_WKTargetedElementInfo isPseudoElement]):

Add a new SPI property to surface whether or not the targeted element is a pseudo-element to the
client.

Canonical link: https://commits.webkit.org/276816@main
  • Loading branch information
whsieh committed Mar 29, 2024
1 parent 26bc2e2 commit 780bd12
Show file tree
Hide file tree
Showing 19 changed files with 219 additions and 22 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<!DOCTYPE html> <!-- webkit-test-runner [ useFlexibleViewport=true ] -->
<html>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1">
</head>
<body>This test requires WebKitTestRunner</body>
</html>
46 changes: 46 additions & 0 deletions LayoutTests/fast/element-targeting/target-pseudo-elements.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
<!DOCTYPE html> <!-- webkit-test-runner [ useFlexibleViewport=true ] -->
<html>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1">
<script src="../../resources/ui-helper.js"></script>
<style>
body::after {
top: 0;
left: 0;
z-index: 100;
}

body::before {
bottom: 0;
right: 0;
z-index: 10;
}

body::before, body::after {
position: fixed;
display: block;
content: " ";
width: 128px;
height: 128px;
background-image: url(../images/resources/green-256x256.png);
background-repeat: no-repeat;
background-size: 128px 128px;
}
</style>
</head>
<body>This test requires WebKitTestRunner</body>
<script>
addEventListener("load", async event => {
testRunner.waitUntilDone();
const firstSelector = await UIHelper.adjustVisibilityForFrontmostTarget(64, 64);
const secondSelector = await UIHelper.adjustVisibilityForFrontmostTarget(innerWidth - 64, innerHeight - 64);

if (firstSelector.toLowerCase() !== "body::after")
document.writeln(`FAIL: first selector was ${firstSelector}`);

if (secondSelector.toLowerCase() !== "body::before")
document.writeln(`FAIL: second selector was ${secondSelector}`);
testRunner.notifyDone();
});
</script>
</html>
1 change: 1 addition & 0 deletions Source/WebCore/Headers.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -1069,6 +1069,7 @@ set(WebCore_PRIVATE_FRAMEWORK_HEADERS
dom/UserTypingGestureIndicator.h
dom/ValidityStateFlags.h
dom/ViewTransition.h
dom/VisibilityAdjustment.h
dom/ViewTransitionUpdateCallback.h
dom/ViewportArguments.h
dom/VisibilityChangeClient.h
Expand Down
4 changes: 4 additions & 0 deletions Source/WebCore/WebCore.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -6309,6 +6309,7 @@
F4E90A3C2B52038E002DA469 /* PlatformTextAlternatives.h in Headers */ = {isa = PBXBuildFile; fileRef = F4E90A3B2B52038E002DA469 /* PlatformTextAlternatives.h */; settings = {ATTRIBUTES = (Private, ); }; };
F4E9E2472A23C77B00A5B134 /* OpportunisticTaskScheduler.h in Headers */ = {isa = PBXBuildFile; fileRef = F4E9E2452A23C5D000A5B134 /* OpportunisticTaskScheduler.h */; };
F4FB34FC2350C85D00F0094A /* PasteboardCustomData.h in Headers */ = {isa = PBXBuildFile; fileRef = F4FB34FA2350C85D00F0094A /* PasteboardCustomData.h */; settings = {ATTRIBUTES = (Private, ); }; };
F4FC07D62BB3A6220090B808 /* VisibilityAdjustment.h in Headers */ = {isa = PBXBuildFile; fileRef = F4FC07D52BB3A6090090B808 /* VisibilityAdjustment.h */; settings = {ATTRIBUTES = (Private, ); }; };
F50664F8157F52DC00AC226F /* FormController.h in Headers */ = {isa = PBXBuildFile; fileRef = F50664F6157F52DC00AC226F /* FormController.h */; settings = {ATTRIBUTES = (Private, ); }; };
F513A3EA15FF4841001526DB /* ValidationMessageClient.h in Headers */ = {isa = PBXBuildFile; fileRef = F513A3E915FF4841001526DB /* ValidationMessageClient.h */; settings = {ATTRIBUTES = (Private, ); }; };
F544F78915CFB2A800AF33A8 /* PlatformLocale.h in Headers */ = {isa = PBXBuildFile; fileRef = F544F78715CFB2A800AF33A8 /* PlatformLocale.h */; };
Expand Down Expand Up @@ -20243,6 +20244,7 @@
F4FB34FA2350C85D00F0094A /* PasteboardCustomData.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = PasteboardCustomData.h; sourceTree = "<group>"; };
F4FB34FB2350C85D00F0094A /* PasteboardCustomData.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = PasteboardCustomData.cpp; sourceTree = "<group>"; };
F4FB35002350C96200F0094A /* PasteboardCustomDataCocoa.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = PasteboardCustomDataCocoa.mm; sourceTree = "<group>"; };
F4FC07D52BB3A6090090B808 /* VisibilityAdjustment.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = VisibilityAdjustment.h; sourceTree = "<group>"; };
F50664F5157F52DC00AC226F /* FormController.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = FormController.cpp; sourceTree = "<group>"; };
F50664F6157F52DC00AC226F /* FormController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FormController.h; sourceTree = "<group>"; };
F513A3E915FF4841001526DB /* ValidationMessageClient.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ValidationMessageClient.h; sourceTree = "<group>"; };
Expand Down Expand Up @@ -37133,6 +37135,7 @@
49ECA8412B0C8FA70072F447 /* ViewTransition.idl */,
49ECA8422B0C8FA70072F447 /* ViewTransitionUpdateCallback.h */,
49ECA83E2B0C8FA50072F447 /* ViewTransitionUpdateCallback.idl */,
F4FC07D52BB3A6090090B808 /* VisibilityAdjustment.h */,
83407FC01E8D9C1200E048D3 /* VisibilityChangeClient.h */,
46CA9C411F97BBE7004CFC3A /* VisibilityState.h */,
46CA9C421F97BBE7004CFC3A /* VisibilityState.idl */,
Expand Down Expand Up @@ -42824,6 +42827,7 @@
713516A22AE6EF3500718EBE /* ViewTimeline.h in Headers */,
713516A32AE6EF3A00718EBE /* ViewTimelineOptions.h in Headers */,
7A10958A28C7CEB20056F3BE /* ViolationReportType.h in Headers */,
F4FC07D62BB3A6220090B808 /* VisibilityAdjustment.h in Headers */,
83407FC11E8D9C1700E048D3 /* VisibilityChangeClient.h in Headers */,
46CA9C441F97BBE9004CFC3A /* VisibilityState.h in Headers */,
93309E20099E64920056E581 /* VisiblePosition.h in Headers */,
Expand Down
11 changes: 7 additions & 4 deletions Source/WebCore/dom/Element.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,7 @@
#include "TextIterator.h"
#include "TouchAction.h"
#include "TypedElementDescendantIteratorInlines.h"
#include "VisibilityAdjustment.h"
#include "VoidCallback.h"
#include "WebAnimation.h"
#include "WebAnimationTypes.h"
Expand Down Expand Up @@ -5635,14 +5636,16 @@ CustomStateSet& Element::ensureCustomStateSet()
return *rareData.customStateSet();
}

bool Element::isVisibilityAdjustmentRoot() const
OptionSet<VisibilityAdjustment> Element::visibilityAdjustment() const
{
return hasRareData() && elementRareData()->isVisibilityAdjustmentRoot();
if (!hasRareData())
return { };
return elementRareData()->visibilityAdjustment();
}

void Element::setIsVisibilityAdjustmentRoot()
void Element::addVisibilityAdjustment(OptionSet<VisibilityAdjustment> adjustment)
{
ensureElementRareData().setIsVisibilityAdjustmentRoot();
ensureElementRareData().addVisibilityAdjustment(adjustment);
}

} // namespace WebCore
5 changes: 3 additions & 2 deletions Source/WebCore/dom/Element.h
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@ enum class IsSyntheticClick : bool { No, Yes };
enum class ParserContentPolicy : uint8_t;
enum class ResolveURLs : uint8_t { No, NoExcludingURLsForPrivacy, Yes, YesExcludingURLsForPrivacy };
enum class SelectionRestorationMode : uint8_t;
enum class VisibilityAdjustment : uint8_t;

struct CheckVisibilityOptions;
struct FullscreenOptions;
Expand Down Expand Up @@ -632,8 +633,8 @@ class Element : public ContainerNode {
WEBCORE_EXPORT void requestPointerLock();
#endif

bool isVisibilityAdjustmentRoot() const;
void setIsVisibilityAdjustmentRoot();
OptionSet<VisibilityAdjustment> visibilityAdjustment() const;
void addVisibilityAdjustment(OptionSet<VisibilityAdjustment>);

bool isSpellCheckingEnabled() const;
WEBCORE_EXPORT bool isWritingSuggestionsEnabled() const;
Expand Down
2 changes: 1 addition & 1 deletion Source/WebCore/dom/ElementRareData.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ struct SameSizeAsElementRareData : NodeRareData {
void* resizeObserverData;
Markable<LayoutUnit, LayoutUnitMarkableTraits> lastRemembedSize[2];
ExplicitlySetAttrElementsMap explicitlySetAttrElementsMap;
bool isVisibilityAdjustmentRoot;
uint8_t visibilityAdjustment;
};

static_assert(sizeof(ElementRareData) == sizeof(SameSizeAsElementRareData), "ElementRareData should stay small");
Expand Down
7 changes: 4 additions & 3 deletions Source/WebCore/dom/ElementRareData.h
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@
#include "SpaceSplitString.h"
#include "StylePropertyMap.h"
#include "StylePropertyMapReadOnly.h"
#include "VisibilityAdjustment.h"
#include <wtf/Markable.h>

namespace WebCore {
Expand Down Expand Up @@ -153,8 +154,8 @@ class ElementRareData : public NodeRareData {
CustomStateSet* customStateSet() { return m_customStateSet.get(); }
void setCustomStateSet(Ref<CustomStateSet>&& customStateSet) { m_customStateSet = WTFMove(customStateSet); }

bool isVisibilityAdjustmentRoot() const { return m_isVisibilityAdjustmentRoot; }
void setIsVisibilityAdjustmentRoot() { m_isVisibilityAdjustmentRoot = true; }
OptionSet<VisibilityAdjustment> visibilityAdjustment() const { return m_visibilityAdjustment; }
void addVisibilityAdjustment(OptionSet<VisibilityAdjustment> adjustment) { m_visibilityAdjustment.add(adjustment); }

#if DUMP_NODE_STATISTICS
OptionSet<UseType> useTypes() const
Expand Down Expand Up @@ -259,7 +260,7 @@ class ElementRareData : public NodeRareData {

RefPtr<CustomStateSet> m_customStateSet;

bool m_isVisibilityAdjustmentRoot { false };
OptionSet<VisibilityAdjustment> m_visibilityAdjustment;
};

inline ElementRareData::ElementRareData()
Expand Down
36 changes: 36 additions & 0 deletions Source/WebCore/dom/VisibilityAdjustment.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
/*
* Copyright (C) 2024 Apple Inc. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
* THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
* BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
* THE POSSIBILITY OF SUCH DAMAGE.
*/

#pragma once

namespace WebCore {

enum class VisibilityAdjustment : uint8_t {
Subtree = 1 << 0,
BeforePseudo = 1 << 1,
AfterPseudo = 1 << 2,
};

} // namespace WebCore
96 changes: 85 additions & 11 deletions Source/WebCore/page/ElementTargetingController.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -42,11 +42,13 @@
#include "LocalFrameView.h"
#include "NodeList.h"
#include "Page.h"
#include "PseudoElement.h"
#include "Region.h"
#include "RenderDescendantIterator.h"
#include "RenderView.h"
#include "TextExtraction.h"
#include "TypedElementDescendantIteratorInlines.h"
#include "VisibilityAdjustment.h"

namespace WebCore {

Expand Down Expand Up @@ -171,6 +173,29 @@ static String parentRelativeSelectorRecursive(Element& element)
// Returns multiple CSS selectors that uniquely match the target element.
static Vector<String> selectorsForTarget(Element& element)
{
if (RefPtr pseudoElement = dynamicDowncast<PseudoElement>(element)) {
RefPtr host = pseudoElement->hostElement();
if (!host)
return { };

auto pseudoSelector = [&]() -> String {
if (element.isBeforePseudoElement())
return "::before"_s;

if (element.isAfterPseudoElement())
return "::after"_s;

return { };
}();

if (pseudoSelector.isEmpty())
return { };

return selectorsForTarget(*host).map([&](auto hostSelector) {
return makeString(hostSelector, pseudoSelector);
});
}

Vector<String> selectors;
if (auto selector = computeIDSelector(element); !selector.isEmpty())
selectors.append(WTFMove(selector));
Expand Down Expand Up @@ -224,6 +249,7 @@ static TargetedElementInfo targetedElementInfo(Element& element, IsUnderPoint is
.positionType = renderer->style().position(),
.childFrameIdentifiers = collectChildFrameIdentifiers(element),
.isUnderPoint = isUnderPoint == IsUnderPoint::Yes,
.isPseudoElement = element.isPseudoElement(),
};
}

Expand All @@ -249,6 +275,11 @@ static bool isTargetCandidate(Element& element, const HTMLElement* onlyMainEleme
if (!element.renderer())
return false;

if (element.isBeforePseudoElement() || element.isAfterPseudoElement()) {
// We don't need to worry about affecting main content if we're only adjusting pseudo elements.
return true;
}

if (&element == element.document().body())
return false;

Expand Down Expand Up @@ -425,6 +456,42 @@ static FloatRect computeClientRect(RenderObject& renderer)
return rect;
}

static inline Element& elementToAdjust(Element& element)
{
if (RefPtr pseudoElement = dynamicDowncast<PseudoElement>(element)) {
if (RefPtr host = pseudoElement->hostElement())
return *host;
}
return element;
}

static inline VisibilityAdjustment adjustmentToApply(Element& element)
{
if (element.isAfterPseudoElement())
return VisibilityAdjustment::AfterPseudo;

if (element.isBeforePseudoElement())
return VisibilityAdjustment::BeforePseudo;

return VisibilityAdjustment::Subtree;
}

struct VisibilityAdjustmentResult {
RefPtr<Element> adjustedElement;
bool invalidateSubtree { false };
};

static inline VisibilityAdjustmentResult adjustVisibilityIfNeeded(Element& element)
{
Ref adjustedElement = elementToAdjust(element);
auto adjustment = adjustmentToApply(element);
if (adjustedElement->visibilityAdjustment().contains(adjustment))
return { };

adjustedElement->addVisibilityAdjustment(adjustment);
return { adjustedElement.ptr(), adjustment == VisibilityAdjustment::Subtree };
}

bool ElementTargetingController::adjustVisibility(const Vector<Ref<Element>>& elements)
{
RefPtr page = m_page.get();
Expand All @@ -446,13 +513,16 @@ bool ElementTargetingController::adjustVisibility(const Vector<Ref<Element>>& el

bool changed = false;
for (auto& element : elements) {
if (element->isVisibilityAdjustmentRoot())
continue;

CheckedPtr renderer = element->renderer();
if (!renderer)
continue;

auto [adjustedElement, invalidateSubtree] = adjustVisibilityIfNeeded(element);
if (!adjustedElement)
continue;

changed = true;

auto clientRect = computeClientRect(*renderer);
if (renderer->isOutOfFlowPositioned() && clientRect.area() / viewportArea < maximumAreaRatioForTrackingAdjustmentAreas) {
auto enclosingClientRect = enclosingIntRect(clientRect);
Expand All @@ -465,9 +535,10 @@ bool ElementTargetingController::adjustVisibility(const Vector<Ref<Element>>& el
m_viewportSizeForVisibilityAdjustment = viewportSize;
}

element->setIsVisibilityAdjustmentRoot();
element->invalidateStyleAndRenderersForSubtree();
changed = true;
if (invalidateSubtree)
adjustedElement->invalidateStyleAndRenderersForSubtree();
else
adjustedElement->invalidateStyle();
}
return changed;
}
Expand Down Expand Up @@ -555,9 +626,6 @@ void ElementTargetingController::adjustVisibilityInRepeatedlyTargetedRegions(Doc
if (!element)
continue;

if (element->isVisibilityAdjustmentRoot())
continue;

if (!renderer.isVisibleInDocumentRect(visibleDocumentRect))
continue;

Expand All @@ -571,8 +639,14 @@ void ElementTargetingController::adjustVisibilityInRepeatedlyTargetedRegions(Doc
}

for (auto& element : elementsToAdjust) {
element->setIsVisibilityAdjustmentRoot();
element->invalidateStyleAndRenderersForSubtree();
auto [adjustedElement, invalidateSubtree] = adjustVisibilityIfNeeded(element);
if (!adjustedElement)
continue;

if (invalidateSubtree)
adjustedElement->invalidateStyleAndRenderersForSubtree();
else
adjustedElement->invalidateStyle();
}
}

Expand Down
1 change: 1 addition & 0 deletions Source/WebCore/page/ElementTargetingTypes.h
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ struct TargetedElementInfo {
PositionType positionType { PositionType::Static };
Vector<FrameIdentifier> childFrameIdentifiers;
bool isUnderPoint { true };
bool isPseudoElement { false };
};

} // namespace WebCore
Loading

0 comments on commit 780bd12

Please sign in to comment.