blob: 6c5f3646af6a18ec1ee29cd6bdf8658a18049d59 [file] [log] [blame] [edit]
/*
* Copyright (C) 2025 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.
*/
#import "config.h"
#if ENABLE(SWIFTUI)
#import "PlatformUtilities.h"
#import "Test.h"
#import "TestCocoa.h"
#import "TestWKWebView.h"
#import "Utilities.h"
#import <wtf/RetainPtr.h>
#if PLATFORM(MAC)
#import <pal/spi/mac/NSScrollerImpSPI.h>
#endif
@interface WKWebView (ScrollGeometryTesting)
- (void)_setNeedsScrollGeometryUpdates:(BOOL)needsScrollGeometryUpdates;
@end
@class WKScrollGeometry;
@interface TestScrollGeometryDelegate : NSObject <WKUIDelegate>
- (void)_webView:(WKWebView *)webView geometryDidChange:(WKScrollGeometry *)geometry;
- (CGSize)currentContentSize;
@end
@implementation TestScrollGeometryDelegate {
RetainPtr<WKScrollGeometry> _currentGeometry;
}
- (void)_webView:(WKWebView *)webView geometryDidChange:(WKScrollGeometry *)geometry
{
_currentGeometry = geometry;
}
- (CGSize)currentContentSize
{
return [_currentGeometry contentSize];
}
@end
static void runContentSizeTest(NSString *html, CGSize expectedSize, BOOL needsScrollGeometryUpdates = YES)
{
RetainPtr webView = adoptNS([[TestWKWebView alloc] initWithFrame:NSMakeRect(0, 0, 800, 600)]);
[webView _setNeedsScrollGeometryUpdates:needsScrollGeometryUpdates];
RetainPtr delegate = adoptNS([[TestScrollGeometryDelegate alloc] init]);
[webView setUIDelegate:delegate.get()];
[webView synchronouslyLoadHTMLString:[NSString stringWithFormat:@"<!DOCTYPE html>%@", html]];
[webView waitForNextPresentationUpdate];
EXPECT_EQ([delegate currentContentSize], expectedSize);
[webView synchronouslyLoadHTMLString:html];
[webView waitForNextPresentationUpdate];
EXPECT_EQ([delegate currentContentSize], expectedSize);
}
TEST(WKScrollGeometry, NoScrollGeometryUpdates)
{
runContentSizeTest(@""
"<html>"
"<meta name='viewport' content='width=device-width, initial-scale=1'/>"
"<head>"
"<style>"
" div { background: red; height: 10000px; }"
"</style>"
"</head>"
"<body>"
"<div>Test</div>"
"</body>"
"</html>", CGSizeZero, NO);
}
TEST(WKScrollGeometry, ContentSizeTallerThanWebView)
{
CGFloat expectedWidth = 800;
#if PLATFORM(MAC)
auto scrollbarWidth = [[NSScrollerImp self] scrollerWidthForControlSize:NSControlSizeRegular scrollerStyle:NSScroller.preferredScrollerStyle];
expectedWidth -= scrollbarWidth;
#endif
runContentSizeTest(@""
"<html>"
"<head>"
"<meta name='viewport' content='width=device-width, initial-scale=1'/>"
"<style>"
" div { background: red; height: 10000px; }"
"</style>"
"</head>"
"<body>"
"<div>Test</div>"
"</body>"
"</html>", CGSizeMake(expectedWidth - 8, 10016));
}
TEST(WKScrollGeometry, FixedSizeDiv)
{
runContentSizeTest(@""
"<html>"
"<head>"
"<meta name='viewport' content='width=device-width, initial-scale=1'/>"
"<style>"
" div { background: red; width: 200px; height: 200px; }"
"</style>"
"</head>"
"<body>"
"<div></div>"
"</body>"
"</html>", CGSizeMake(208, 216));
}
TEST(WKScrollGeometry, AbsolutePositionedRootElement)
{
#if PLATFORM(IOS_FAMILY)
CGFloat expectedHeight = 136;
#else
CGFloat expectedHeight = 134;
#endif
runContentSizeTest(@""
"<html>"
"<head>"
"<meta name='viewport' content='width=device-width, initial-scale=1'/>"
"<style>"
" html { position: absolute; top: 100px; }"
"</style>"
"</head>"
"<body>"
"<div>Test</div>"
"</body>"
"</html>", CGSizeMake(34, expectedHeight));
}
TEST(WKScrollGeometry, SimpleText)
{
#if PLATFORM(IOS_FAMILY)
CGFloat expectedHeight = 36;
#else
CGFloat expectedHeight = 34;
#endif
runContentSizeTest(@""
"<html>"
"<head>"
"<meta name='viewport' content='width=device-width, initial-scale=1'/>"
"</head>"
"<body>"
"<div>Test</div>"
"</body>"
"</html>", CGSizeMake(792, expectedHeight));
}
TEST(WKScrollGeometry, FixedHeightRootElementWithTallAbsolutePositionedDiv)
{
runContentSizeTest(@""
"<html>"
"<head>"
"<meta name='viewport' content='width=device-width, initial-scale=1'/>"
"<style>"
" html { height: 200px; }"
" div { background: red; width: 20px; height: 10000px; position: absolute; }"
"</style>"
"</head>"
"<body>"
"<div>Test</div>"
"</body>"
"</html>", CGSizeMake(34, 10008));
}
TEST(WKScrollGeometry, ClippedRootElementWithFixedHeightBodyAndTallDiv)
{
runContentSizeTest(@""
"<html>"
"<head>"
"<meta name='viewport' content='width=device-width, initial-scale=1'/>"
"<style>"
" html { overflow: clip; }"
" body { height: 200px; }"
" div { background: red; width: 20px; height: 10000px; }"
"</style>"
"</head>"
"<body>"
"<div>Test</div>"
"</body>"
"</html>", CGSizeMake(34, 600));
}
TEST(WKScrollGeometry, NestedDivsWithAbsolutePositioning)
{
runContentSizeTest(@""
"<html>"
"<head>"
"<meta name='viewport' content='width=device-width, initial-scale=1'/>"
"<style>"
" #outer { position: absolute; top: 100px; }"
" #inner { background: red; width: 200px; height: 200px; }"
"</style>"
"</head>"
"<body>"
"<div id='outer'><div id='inner'></div></div>"
"</body>"
"</html>", CGSizeMake(208, 300));
}
TEST(WKScrollGeometry, NestedDivsWithMargin)
{
runContentSizeTest(@""
"<html>"
"<head>"
"<meta name='viewport' content='width=device-width, initial-scale=1'/>"
"<style>"
" #outer { background: red; width: 200px; height: 200px; }"
" #inner { background: blue; width: 200px; height: 200px; margin: 8px 8px 8px 8px; }"
"</style>"
"</head>"
"<body>"
"<div id='outer'><div id='inner'></div></div>"
"</body>"
"</html>", CGSizeMake(216, 216));
}
TEST(WKScrollGeometry, NestedDivsWithAbsolutePositioningAndClipOnOutermostDiv)
{
runContentSizeTest(@""
"<html>"
"<head>"
"<meta name='viewport' content='width=device-width, initial-scale=1'/>"
"<style>"
" html { height: 200px; overflow: clip; }"
" #outer { background: red; width: 20px; height: 300px; position: absolute; overflow: clip; }"
" #inner { background: blue; height: 10000px; width: 20px; }"
"</style>"
"</head>"
"<body>"
"<div id='outer'>Outer<div id='inner'>Inner</div></div>"
"</body>"
"</html>", CGSizeMake(28, 308));
}
TEST(WKScrollGeometry, NestedDivsWithAbsolutePositioningAndClipOnInnermostDiv)
{
runContentSizeTest(@""
"<html>"
"<head>"
"<meta name='viewport' content='width=device-width, initial-scale=1'/>"
"<style>"
" #outer { background: red; width: 400px; height: 400px; overflow: clip; }"
" #inner { background: blue; width: 500px; height: 600px; }"
" #inner-inner { background: green; width: 700px; height: 600px; position: absolute; top: 5px; left: 100px; }"
"</style>"
"</head>"
"<body>"
"<div id='outer'>Test<div id='inner'>B<div id='inner-inner'>C</div></div></div>"
"</body>"
"</html>", CGSizeMake(800, 605));
}
TEST(WKScrollGeometry, NestedDivsRelativePosition)
{
runContentSizeTest(@""
"<html>"
"<head>"
"<meta name='viewport' content='width=device-width, initial-scale=1'/>"
"<style>"
" #outer { background: red; width: 400px; height: 400px; position: relative; }"
" #inner { background: blue; width: 500px; height: 600px; }"
" #inner-inner { background: green; width: 700px; height: 600px; position: absolute; top: 100px; left: 100px; }"
"</style>"
"</head>"
"<body>"
"<div id='outer'>Test<div id='inner'>B<div id='inner-inner'>C</div></div></div>"
"</body>"
"</html>", CGSizeMake(808, 708));
}
TEST(WKScrollGeometry, NestedDivsRelativePositionAndClip)
{
runContentSizeTest(@""
"<html>"
"<head>"
"<meta name='viewport' content='width=device-width, initial-scale=1'/>"
"<style>"
" #outer { background: red; width: 400px; height: 400px; overflow: clip; position: relative; }"
" #inner { background: blue; width: 500px; height: 600px; }"
" #inner-inner { background: green; width: 700px; height: 600px; position: absolute; top: 100px; left: 100px; }"
"</style>"
"</head>"
"<body>"
"<div id='outer'>Test<div id='inner'>B<div id='inner-inner'>C</div></div></div>"
"</body>"
"</html>", CGSizeMake(408, 408));
}
TEST(WKScrollGeometry, NestedDivsOverflowYClip)
{
runContentSizeTest(@""
"<html>"
"<head>"
"<meta name='viewport' content='width=device-width, initial-scale=1'/>"
"<style>"
" #outer { background: red; width: 400px; height: 400px; overflow-y: clip; }"
" #inner { background: blue; width: 500px; height: 600px; }"
" #inner-inner { background: green; width: 700px; height: 600px; }"
"</style>"
"</head>"
"<body>"
"<div id='outer'>Test<div id='inner'>B<div id='inner-inner'>C</div></div></div>"
"</body>"
"</html>", CGSizeMake(708, 416));
}
TEST(WKScrollGeometry, NestedDivsOverflowYScroll)
{
runContentSizeTest(@""
"<html>"
"<head>"
"<meta name='viewport' content='width=device-width, initial-scale=1'/>"
"<style>"
" #outer { width: 200px; height: 200px; overflow-y: scroll; }"
" #inner { background: red; height: 10000px; width: 400px; }"
"</style>"
"</head>"
"<body>"
"<div id='outer'><div id='inner'></div></div>"
"</body>"
"</html>", CGSizeMake(208, 216));
}
TEST(WKScrollGeometry, FixedSizeBody)
{
runContentSizeTest(@""
"<html>"
"<head>"
"<meta name='viewport' content='width=device-width, initial-scale=1'/>"
"<style>"
" body { width: 400px; height: 400px; }"
" #outer { background: red; width: 200px; height: 200px; }"
"</style>"
"</head>"
"<body>"
"<div id='outer'><div id='inner'>Test</div></div>"
"</body>"
"</html>", CGSizeMake(408, 416));
}
TEST(WKScrollGeometry, MarginBottom)
{
runContentSizeTest(@""
"<html>"
"<head>"
"<meta name='viewport' content='width=device-width, initial-scale=1'/>"
"<style>"
" #inner { background: red; width: 200px; height: 200px; margin-bottom: 50px; }"
"</style>"
"</head>"
"<body>"
"<div id='outer'><div id='inner'>Test</div></div>"
"</body>"
"</html>", CGSizeMake(792, 258));
}
TEST(WKScrollGeometry, MarginBottomCollapsePrevented)
{
runContentSizeTest(@""
"<html>"
"<head>"
"<meta name='viewport' content='width=device-width, initial-scale=1'/>"
"<style>"
" #outer { border: 1px solid black; }"
" #inner { background: red; width: 200px; height: 200px; margin-bottom: 50px; }"
"</style>"
"</head>"
"<body>"
"<div id='outer'><div id='inner'>Test</div></div>"
"</body>"
"</html>", CGSizeMake(792, 268));
}
#endif // ENABLE(SWIFTUI)