Skip to content

Commit

Permalink
[Remote Inspection] _WKTargetedElementInfo should expose a method to …
Browse files Browse the repository at this point in the history
…get contained subframes

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

Reviewed by Megan Gardner.

Add a new method on `_WKTargetedElementInfo` to request `_WKFrameTreeNode`s that correspond to the
subframes underneath the targeted element. See below for more details.

* Source/WebCore/page/ElementTargeting.cpp:
(WebCore::collectChildFrameIdentifiers):
(WebCore::targetedElementInfo):

Plumb frame IDs for subframes underneath the targeted element from the web process to the UI
process.

* Source/WebCore/page/ElementTargetingTypes.h:
* Source/WebKit/Shared/API/APIObject.h:

Drive-by fix a typo in a comment: `namespace Object` should be `namespace API`.

* Source/WebKit/Shared/WebCoreArgumentCoders.serialization.in:
* Source/WebKit/UIProcess/API/APIFrameInfo.cpp:
(API::FrameInfo::title const):

Add a getter for `title` on `FrameInfo`, which calls into `WebFrameProxy`.

* Source/WebKit/UIProcess/API/APIFrameInfo.h:
* Source/WebKit/UIProcess/API/APITargetedElementInfo.cpp:
(API::TargetedElementInfo::isSameElement const):
(API::TargetedElementInfo::childFrames const):
* Source/WebKit/UIProcess/API/APITargetedElementInfo.h:
* Source/WebKit/UIProcess/API/Cocoa/WKFrameInfo.mm:
(-[WKFrameInfo _title]):

Add a new SPI method on `WKFrameInfo`, which allows clients to read the last cached `title` for the
given frame.

* Source/WebKit/UIProcess/API/Cocoa/WKFrameInfoPrivate.h:
* Source/WebKit/UIProcess/API/Cocoa/_WKTargetedElementInfo.h:
* Source/WebKit/UIProcess/API/Cocoa/_WKTargetedElementInfo.mm:
(-[_WKTargetedElementInfo getChildFrames:]):

Implement the new API method. Using the list of subframe IDs from the targeting results, request
`FrameTreeNodeData` for each subframe and call the completion handler once all the async calls are
complete.

* Tools/TestWebKitAPI/TestWebKitAPI.xcodeproj/project.pbxproj:
* Tools/TestWebKitAPI/Tests/WebKitCocoa/ElementTargetingTests.mm:
(-[_WKTargetedElementInfo childFrames]):
(TestWebKitAPI::TEST):
* Tools/TestWebKitAPI/Tests/WebKitCocoa/element-targeting-1.html:
* Tools/TestWebKitAPI/Tests/WebKitCocoa/nested-frames.html: Added.

Augment an existing API test to cover this new method as well.

Canonical link: https://commits.webkit.org/276647@main
  • Loading branch information
whsieh committed Mar 25, 2024
1 parent 7bac523 commit c33f546
Show file tree
Hide file tree
Showing 16 changed files with 211 additions and 21 deletions.
13 changes: 12 additions & 1 deletion Source/WebCore/page/ElementTargeting.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,16 @@ static inline RectEdges<bool> computeOffsetEdges(const RenderStyle& style)
};
}

static inline Vector<FrameIdentifier> collectChildFrameIdentifiers(Element& element)
{
Vector<FrameIdentifier> identifiers;
for (auto& owner : descendantsOfType<HTMLFrameOwnerElement>(element)) {
if (RefPtr frame = owner.protectedContentFrame())
identifiers.append(frame->frameID());
}
return identifiers;
}

static TargetedElementInfo targetedElementInfo(Element& element)
{
CheckedPtr renderer = element.renderer();
Expand All @@ -196,7 +206,8 @@ static TargetedElementInfo targetedElementInfo(Element& element)
.renderedText = TextExtraction::extractRenderedText(element),
.selectors = selectorsForTarget(element),
.boundsInRootView = element.boundingBoxInRootViewCoordinates(),
.positionType = renderer->style().position()
.positionType = renderer->style().position(),
.childFrameIdentifiers = collectChildFrameIdentifiers(element),
};
}

Expand Down
2 changes: 2 additions & 0 deletions Source/WebCore/page/ElementTargetingTypes.h
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
#include "ElementIdentifier.h"
#include "FloatPoint.h"
#include "FloatRect.h"
#include "FrameIdentifier.h"
#include "RectEdges.h"
#include "RenderStyleConstants.h"
#include "ScriptExecutionContextIdentifier.h"
Expand All @@ -48,6 +49,7 @@ struct TargetedElementInfo {
Vector<String> selectors;
FloatRect boundsInRootView;
PositionType positionType { PositionType::Static };
Vector<FrameIdentifier> childFrameIdentifiers;
};

} // namespace WebCore
2 changes: 1 addition & 1 deletion Source/WebKit/Shared/API/APIObject.h
Original file line number Diff line number Diff line change
Expand Up @@ -302,7 +302,7 @@ inline API::Object* Object::unwrap(void* object)
}
#endif

} // namespace Object
} // namespace API

#undef DELEGATE_REF_COUNTING_TO_COCOA

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -897,6 +897,7 @@ header: <WebCore/ElementTargetingTypes.h>
Vector<String> selectors
WebCore::FloatRect boundsInRootView
WebCore::PositionType positionType
Vector<WebCore::FrameIdentifier> childFrameIdentifiers
};

header: <WebCore/RenderStyleConstants.h>
Expand Down
11 changes: 11 additions & 0 deletions Source/WebKit/UIProcess/API/APIFrameInfo.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -57,4 +57,15 @@ RefPtr<FrameHandle> FrameInfo::parentFrameHandle() const
return FrameHandle::create(*m_data.parentFrameID);
}

WTF::String FrameInfo::title() const
{
if (!m_page)
return { };

if (RefPtr frame = WebKit::WebFrameProxy::webFrame(m_data.frameID); frame && frame->page() == m_page)
return frame->title();

return { };
}

} // namespace API
1 change: 1 addition & 0 deletions Source/WebKit/UIProcess/API/APIFrameInfo.h
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ class FrameInfo final : public ObjectImpl<Object::Type::FrameInfo> {
ProcessID processID() const { return m_data.processID; }
bool isFocused() const { return m_data.isFocused; }
bool errorOccurred() const { return m_data.errorOccurred; }
WTF::String title() const;

private:
FrameInfo(WebKit::FrameInfoData&&, RefPtr<WebKit::WebPageProxy>&&);
Expand Down
35 changes: 34 additions & 1 deletion Source/WebKit/UIProcess/API/APITargetedElementInfo.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,13 @@
#include "config.h"
#include "APITargetedElementInfo.h"

#include "APIFrameTreeNode.h"
#include "FrameTreeNodeData.h"
#include "PageClient.h"
#include "WebFrameProxy.h"
#include "WebPageProxy.h"
#include <wtf/Box.h>
#include <wtf/CallbackAggregator.h>

namespace API {
using namespace WebKit;
Expand All @@ -41,7 +46,8 @@ TargetedElementInfo::TargetedElementInfo(WebPageProxy& page, WebCore::TargetedEl
bool TargetedElementInfo::isSameElement(const TargetedElementInfo& other) const
{
return m_info.elementIdentifier == other.m_info.elementIdentifier
&& m_info.documentIdentifier == other.m_info.documentIdentifier;
&& m_info.documentIdentifier == other.m_info.documentIdentifier
&& m_page == other.m_page;
}

WebCore::FloatRect TargetedElementInfo::boundsInWebView() const
Expand All @@ -52,4 +58,31 @@ WebCore::FloatRect TargetedElementInfo::boundsInWebView() const
return page->pageClient().rootViewToWebView(boundsInRootView());
}

void TargetedElementInfo::childFrames(CompletionHandler<void(Vector<Ref<FrameTreeNode>>&&)>&& completion) const
{
RefPtr page = m_page.get();
if (!page)
return completion({ });

auto aggregateData = Box<Vector<FrameTreeNodeData>>::create();
auto aggregator = CallbackAggregator::create([page, aggregateData, completion = WTFMove(completion)]() mutable {
completion(WTF::map(WTFMove(*aggregateData), [&](auto&& data) {
return FrameTreeNode::create(WTFMove(data), *page);
}));
});

for (auto identifier : m_info.childFrameIdentifiers) {
RefPtr frame = WebFrameProxy::webFrame(identifier);
if (!frame)
continue;

if (frame->page() != page)
continue;

frame->getFrameInfo([aggregator, aggregateData](auto&& data) {
aggregateData->append(WTFMove(data));
});
}
}

} // namespace API
5 changes: 5 additions & 0 deletions Source/WebKit/UIProcess/API/APITargetedElementInfo.h
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@

#include "APIObject.h"
#include <WebCore/ElementTargetingTypes.h>
#include <wtf/CompletionHandler.h>
#include <wtf/Forward.h>
#include <wtf/WeakPtr.h>

Expand All @@ -36,6 +37,8 @@ class WebPageProxy;

namespace API {

class FrameTreeNode;

class TargetedElementInfo final : public ObjectImpl<Object::Type::TargetedElementInfo> {
public:
static Ref<TargetedElementInfo> create(WebKit::WebPageProxy& page, WebCore::TargetedElementInfo&& info)
Expand All @@ -53,6 +56,8 @@ class TargetedElementInfo final : public ObjectImpl<Object::Type::TargetedElemen
WebCore::FloatRect boundsInRootView() const { return m_info.boundsInRootView; }
WebCore::FloatRect boundsInWebView() const;

void childFrames(CompletionHandler<void(Vector<Ref<FrameTreeNode>>&&)>&&) const;

bool isSameElement(const TargetedElementInfo&) const;

WebCore::ElementIdentifier elementIdentifier() const { return m_info.elementIdentifier; }
Expand Down
5 changes: 5 additions & 0 deletions Source/WebKit/UIProcess/API/Cocoa/WKFrameInfo.mm
Original file line number Diff line number Diff line change
Expand Up @@ -119,4 +119,9 @@ - (BOOL)_errorOccurred
return _frameInfo->errorOccurred();
}

- (NSString *)_title
{
return _frameInfo->title();
}

@end
1 change: 1 addition & 0 deletions Source/WebKit/UIProcess/API/Cocoa/WKFrameInfoPrivate.h
Original file line number Diff line number Diff line change
Expand Up @@ -35,5 +35,6 @@
@property (nonatomic, readonly) BOOL _isLocalFrame WK_API_AVAILABLE(macos(14.0), ios(17.0));
@property (nonatomic, readonly) BOOL _isFocused WK_API_AVAILABLE(macos(WK_MAC_TBA), ios(WK_IOS_TBA));
@property (nonatomic, readonly) BOOL _errorOccurred WK_API_AVAILABLE(macos(WK_MAC_TBA), ios(WK_IOS_TBA));
@property (nonatomic, readonly, copy, nullable) NSString *_title WK_API_AVAILABLE(macos(WK_MAC_TBA), ios(WK_IOS_TBA));

@end
4 changes: 4 additions & 0 deletions Source/WebKit/UIProcess/API/Cocoa/_WKTargetedElementInfo.h
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@ typedef NS_ENUM(NSInteger, _WKTargetedElementPosition) {
_WKTargetedElementPositionFixed
} WK_API_AVAILABLE(macos(WK_MAC_TBA), ios(WK_IOS_TBA));

@class _WKFrameTreeNode;

WK_CLASS_AVAILABLE(macos(WK_MAC_TBA), ios(WK_IOS_TBA))
@interface _WKTargetedElementInfo : NSObject

Expand All @@ -47,6 +49,8 @@ WK_CLASS_AVAILABLE(macos(WK_MAC_TBA), ios(WK_IOS_TBA))

- (BOOL)isSameElement:(_WKTargetedElementInfo *)other;

- (void)getChildFrames:(void(^)(NSArray<_WKFrameTreeNode *> *))completionHandler;

@end

NS_ASSUME_NONNULL_END
14 changes: 14 additions & 0 deletions Source/WebKit/UIProcess/API/Cocoa/_WKTargetedElementInfo.mm
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,13 @@
#import "config.h"
#import "_WKTargetedElementInfo.h"

#import "APIFrameTreeNode.h"
#import "WKObject.h"
#import "WebPageProxy.h"
#import "_WKFrameTreeNodeInternal.h"
#import "_WKTargetedElementInfoInternal.h"
#import <WebCore/WebCoreObjCExtras.h>
#import <wtf/BlockPtr.h>
#import <wtf/cocoa/VectorCocoa.h>

@implementation _WKTargetedElementInfo
Expand Down Expand Up @@ -91,6 +96,15 @@ - (_WKRectEdge)offsetEdges
return edges;
}

- (void)getChildFrames:(void(^)(NSArray<_WKFrameTreeNode *> *))completion
{
return _info->childFrames([completion = makeBlockPtr(completion)](auto&& nodes) {
completion(createNSArray(WTFMove(nodes), [](API::FrameTreeNode& node) {
return wrapper(node);
}).autorelease());
});
}

- (BOOL)isSameElement:(_WKTargetedElementInfo *)other
{
return _info->isSameElement(*other->_info);
Expand Down
4 changes: 4 additions & 0 deletions Tools/TestWebKitAPI/TestWebKitAPI.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -1183,6 +1183,7 @@
F4512E131F60C44600BB369E /* DataTransferItem-getAsEntry.html in Copy Resources */ = {isa = PBXBuildFile; fileRef = F4512E121F60C43400BB369E /* DataTransferItem-getAsEntry.html */; };
F4517B672054C49500C26721 /* TestWKWebViewController.mm in Sources */ = {isa = PBXBuildFile; fileRef = F4517B662054C49500C26721 /* TestWKWebViewController.mm */; };
F4538EF71E8473E600B5C953 /* large-red-square.png in Copy Resources */ = {isa = PBXBuildFile; fileRef = F4538EF01E846B4100B5C953 /* large-red-square.png */; };
F455DB952BAE45B600732E1B /* nested-frames.html in Copy Resources */ = {isa = PBXBuildFile; fileRef = F455DB8C2BAE37C100732E1B /* nested-frames.html */; };
F456AB1C213EDBA300CB2CEF /* FontManagerTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = F456AB1B213EDBA300CB2CEF /* FontManagerTests.mm */; };
F457275E25578D06007ACA34 /* DisplayListTestsCG.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F457275D25578D06007ACA34 /* DisplayListTestsCG.cpp */; };
F457A9D6202D68AF00F7E9D5 /* DataTransfer.html in Copy Resources */ = {isa = PBXBuildFile; fileRef = F457A9B3202D535300F7E9D5 /* DataTransfer.html */; };
Expand Down Expand Up @@ -1810,6 +1811,7 @@
9BF356CD202D458500F71160 /* mso-list.html in Copy Resources */,
F42F081227449892007E0D90 /* multiple-images.html in Copy Resources */,
5797FE331EB15AB100B2F4A0 /* navigation-client-default-crypto.html in Copy Resources */,
F455DB952BAE45B600732E1B /* nested-frames.html in Copy Resources */,
2E4838472169DF30002F4531 /* nested-lists.html in Copy Resources */,
C99B675F1E39736F00FC6C80 /* no-autoplay-with-controls.html in Copy Resources */,
466C3843210637DE006A88DE /* notify-resourceLoadObserver.html in Copy Resources */,
Expand Down Expand Up @@ -3548,6 +3550,7 @@
F4517B652054C49500C26721 /* TestWKWebViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = TestWKWebViewController.h; sourceTree = "<group>"; };
F4517B662054C49500C26721 /* TestWKWebViewController.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = TestWKWebViewController.mm; sourceTree = "<group>"; };
F4538EF01E846B4100B5C953 /* large-red-square.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "large-red-square.png"; sourceTree = "<group>"; };
F455DB8C2BAE37C100732E1B /* nested-frames.html */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.html; path = "nested-frames.html"; sourceTree = "<group>"; };
F456AB1B213EDBA300CB2CEF /* FontManagerTests.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = FontManagerTests.mm; sourceTree = "<group>"; };
F457275D25578D06007ACA34 /* DisplayListTestsCG.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = DisplayListTestsCG.cpp; path = cg/DisplayListTestsCG.cpp; sourceTree = "<group>"; };
F457A9B3202D535300F7E9D5 /* DataTransfer.html */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.html; path = DataTransfer.html; sourceTree = "<group>"; };
Expand Down Expand Up @@ -4941,6 +4944,7 @@
9BCD4119206D5ED7001D71BE /* mso-list-on-h4.html */,
9BF356CC202D44F200F71160 /* mso-list.html */,
F42F0811274497C8007E0D90 /* multiple-images.html */,
F455DB8C2BAE37C100732E1B /* nested-frames.html */,
2E4838462169DD42002F4531 /* nested-lists.html */,
466C3842210637CE006A88DE /* notify-resourceLoadObserver.html */,
CDB5DFFE21360ED800D3E189 /* now-playing.html */,
Expand Down
81 changes: 64 additions & 17 deletions Tools/TestWebKitAPI/Tests/WebKitCocoa/ElementTargetingTests.mm
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@

#import "PlatformUtilities.h"
#import "TestWKWebView.h"
#import <WebKit/WKFrameInfoPrivate.h>
#import <WebKit/_WKFrameTreeNode.h>
#import <WebKit/_WKTargetedElementInfo.h>
#import <WebKit/_WKTargetedElementRequest.h>

Expand All @@ -52,35 +54,80 @@ @implementation WKWebView (ElementTargeting)

@end

@interface _WKTargetedElementInfo (TestingAdditions)
@property (nonatomic, readonly) NSArray<_WKFrameTreeNode *> *childFrames;
@end

@implementation _WKTargetedElementInfo (TestingAdditions)

- (NSArray<_WKFrameTreeNode *> *)childFrames
{
__block RetainPtr<NSArray<_WKFrameTreeNode *>> result;
__block bool done = false;
[self getChildFrames:^(NSArray<_WKFrameTreeNode *> *frames) {
result = frames;
done = true;
}];
TestWebKitAPI::Util::run(&done);
return result.autorelease();
}

@end

namespace TestWebKitAPI {

TEST(ElementTargeting, BasicElementTargeting)
{
auto webView = adoptNS([[TestWKWebView alloc] initWithFrame:CGRectMake(0, 0, 800, 600)]);
[webView synchronouslyLoadTestPageNamed:@"element-targeting-1"];

auto elements = [webView targetedElementInfoAt:CGPointMake(150, 150)];
EXPECT_EQ(elements.count, 3U);
Util::waitForConditionWithLogging([&] {
return [[webView objectByEvaluatingJavaScript:@"window.subframeLoaded"] boolValue];
}, 5, @"Timed out waiting for subframes to finish loading.");

RetainPtr elements = [webView targetedElementInfoAt:CGPointMake(150, 150)];
EXPECT_EQ([elements count], 3U);
{
EXPECT_EQ(elements[0].positionType, _WKTargetedElementPositionFixed);
EXPECT_WK_STREQ(".fixed.container", elements[0].selectors.firstObject);
EXPECT_TRUE([elements[0].renderedText containsString:@"The round pegs"]);
EXPECT_EQ(elements[0].renderedText.length, 70U);
EXPECT_EQ(elements[0].offsetEdges, _WKRectEdgeLeft | _WKRectEdgeTop);
auto element = [elements objectAtIndex:0];
EXPECT_EQ(element.positionType, _WKTargetedElementPositionFixed);
EXPECT_WK_STREQ(".fixed.container", element.selectors.firstObject);
EXPECT_TRUE([element.renderedText containsString:@"The round pegs"]);
EXPECT_EQ(element.renderedText.length, 70U);
EXPECT_EQ(element.offsetEdges, _WKRectEdgeLeft | _WKRectEdgeTop);

RetainPtr childFrames = [element childFrames];
EXPECT_EQ([childFrames count], 1U);

auto childFrame = [childFrames firstObject];
EXPECT_FALSE(childFrame.info.mainFrame);
EXPECT_WK_STREQ(childFrame.info.request.URL.lastPathComponent, "nested-frames.html");
EXPECT_WK_STREQ(childFrame.info._title, "Outer Subframe");
EXPECT_EQ(childFrame.childFrames.count, 1U);

auto nestedChildFrame = childFrame.childFrames.firstObject;
EXPECT_FALSE(nestedChildFrame.info.mainFrame);
EXPECT_FALSE(nestedChildFrame.info.mainFrame);
EXPECT_WK_STREQ(nestedChildFrame.info.request.URL.scheme, "about");
EXPECT_WK_STREQ(nestedChildFrame.info._title, "Inner Subframe");
EXPECT_EQ(nestedChildFrame.childFrames.count, 0U);
}
{
EXPECT_EQ(elements[1].positionType, _WKTargetedElementPositionAbsolute);
EXPECT_WK_STREQ("#absolute", elements[1].selectors.firstObject);
EXPECT_TRUE([elements[1].renderedText containsString:@"the crazy ones"]);
EXPECT_EQ(elements[1].renderedText.length, 64U);
EXPECT_EQ(elements[1].offsetEdges, _WKRectEdgeRight | _WKRectEdgeBottom);
auto element = [elements objectAtIndex:1];
EXPECT_EQ(element.positionType, _WKTargetedElementPositionAbsolute);
EXPECT_WK_STREQ("#absolute", element.selectors.firstObject);
EXPECT_TRUE([element.renderedText containsString:@"the crazy ones"]);
EXPECT_EQ(element.renderedText.length, 64U);
EXPECT_EQ(element.offsetEdges, _WKRectEdgeRight | _WKRectEdgeBottom);
EXPECT_EQ(element.childFrames.count, 0U);
}
{
EXPECT_EQ(elements[2].positionType, _WKTargetedElementPositionStatic);
EXPECT_WK_STREQ("MAIN > SECTION:first-of-type", elements[2].selectors.firstObject);
EXPECT_TRUE([elements[2].renderedText containsString:@"Lorem ipsum"]);
EXPECT_EQ(elements[2].renderedText.length, 896U);
EXPECT_EQ(elements[2].offsetEdges, _WKRectEdgeNone);
auto element = [elements objectAtIndex:2];
EXPECT_EQ(element.positionType, _WKTargetedElementPositionStatic);
EXPECT_WK_STREQ("MAIN > SECTION:first-of-type", element.selectors.firstObject);
EXPECT_TRUE([element.renderedText containsString:@"Lorem ipsum"]);
EXPECT_EQ(element.renderedText.length, 896U);
EXPECT_EQ(element.offsetEdges, _WKRectEdgeNone);
EXPECT_EQ(element.childFrames.count, 0U);
}
}

Expand Down
Loading

0 comments on commit c33f546

Please sign in to comment.