[iOS] Content offset jumps erratically when autoscrolling near scroll view content...
authorwenson_hsieh@apple.com <wenson_hsieh@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Fri, 18 Jan 2019 04:15:24 +0000 (04:15 +0000)
committerwenson_hsieh@apple.com <wenson_hsieh@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Fri, 18 Jan 2019 04:15:24 +0000 (04:15 +0000)
commitb6d6d83f1fc3dd002d13b13612b3565f734d3b55
treeb30ed26ccbf879d3bec5b73fcc5cd51376439bc4
parentfe0f3eb548515daaf3f6cda5eab79c630a008d67
[iOS] Content offset jumps erratically when autoscrolling near scroll view content inset areas
https://bugs.webkit.org/show_bug.cgi?id=193494
<rdar://problem/46859627>

Reviewed by Simon Fraser and Tim Horton.

Source/WebCore:

When computing the content offset to scroll to when revealing a given rect in content coordinates, we currently
just use the unobscured content rect. As a result, when scrolling to reveal a rect, we'll clamp the final scroll
position such that only content is visible. For example, when asked to reveal the rect `(0, 0, 1, 1)`, we adjust
the scroll position to be the origin.

However, consider the case where a client (e.g. Mail on iOS) has added a content inset to the web view's scroll
view. If we're asked to reveal a rect that is outside the content area but within a content inset, we will still
end up clamping the scroll position to the unobscured rect. This manifests in a bug where selecting text and
autoscrolling in iOS Mail compose while the scroll view is scrolled all the way to the top to reveal the To/Cc/
Subject fields causes the content offset to jump to the origin, rather than staying at (0, -topContentInset).

To fix this, we teach `RenderLayer::scrollRectToVisible` about content insets that are visible. Rather than use
the content rects as-is, expand to encompass visible content insets as well. This ensures that revealing a
position which is already visible won't cause us to scroll away the content inset area and only show the
unobscured rect.

Tests:  editing/selection/ios/autoscroll-with-top-content-inset.html
        fast/scrolling/ios/scroll-into-view-with-top-content-inset.html

* page/FrameView.cpp:
(WebCore::FrameView::unobscuredContentRectExpandedByContentInsets const):

Introduce a helper method that expands the unobscured content rect to include surrounding content insets.

* page/FrameView.h:
* page/Page.h:
(WebCore::Page::contentInsets const):
(WebCore::Page::setContentInsets):
* rendering/RenderLayer.cpp:
(WebCore::RenderLayer::scrollRectToVisible):
(WebCore::RenderLayer::getRectToExpose const):

Source/WebKit:

Adds `contentInsets` to `VisibleContentRectUpdateInfo`. This keeps track of the visible content insets
surrounding the unobscured content rect. See WebCore ChangeLog for more details.

* Shared/VisibleContentRectUpdateInfo.cpp:
(WebKit::VisibleContentRectUpdateInfo::encode const):
(WebKit::VisibleContentRectUpdateInfo::decode):
(WebKit::operator<<):
* Shared/VisibleContentRectUpdateInfo.h:
(WebKit::VisibleContentRectUpdateInfo::VisibleContentRectUpdateInfo):
(WebKit::VisibleContentRectUpdateInfo::contentInsets const):
(WebKit::operator==):
* UIProcess/API/Cocoa/WKWebView.mm:
(-[WKWebView _computedObscuredInset]):
(-[WKWebView _computedContentInset]):
(-[WKWebView _computedUnobscuredSafeAreaInset]):

We don't care about source compatibility with iOS 10 and below anymore, so we should change these >= iOS 11
target checks to simply `PLATFORM(IOS)`.

(-[WKWebView _updateVisibleContentRects]):

Compute the visible content insets on all sides of the unobscured content rect. These insets are scaled to
content coordinates.

* UIProcess/ios/WKContentView.h:
* UIProcess/ios/WKContentView.mm:
(floatBoxExtent):

Add a helper to convert `UIEdgeInsets` to `WebCore::FloatBoxExtent`, and use it in a few places below.

(-[WKContentView didUpdateVisibleRect:unobscuredRect:contentInsets:unobscuredRectInScrollViewCoordinates:obscuredInsets:unobscuredSafeAreaInsets:inputViewBounds:scale:minimumScale:inStableState:isChangingObscuredInsetsInteractively:enclosedInScrollableAncestorView:]):
(-[WKContentView didUpdateVisibleRect:unobscuredRect:unobscuredRectInScrollViewCoordinates:obscuredInsets:unobscuredSafeAreaInsets:inputViewBounds:scale:minimumScale:inStableState:isChangingObscuredInsetsInteractively:enclosedInScrollableAncestorView:]): Deleted.
* WebProcess/WebPage/ios/WebPageIOS.mm:
(WebKit::WebPage::updateVisibleContentRects):

Update the Page's content insets.

Tools:

Add a new test option to add a top content inset to the test runner's WKWebView's scroll view, and automatically
scroll to reveal this top content inset area when beginning the test (i.e., scroll to (0, -topContentInset)).

* DumpRenderTree/ios/UIScriptControllerIOS.mm:
(WTR::UIScriptController::contentOffsetX const):
(WTR::UIScriptController::contentOffsetY const):
* TestRunnerShared/UIScriptContext/Bindings/UIScriptController.idl:
* TestRunnerShared/UIScriptContext/UIScriptController.cpp:
(WTR::UIScriptController::contentOffsetX const):
(WTR::UIScriptController::contentOffsetY const):
* TestRunnerShared/UIScriptContext/UIScriptController.h:

Also add new UIScriptController methods to ask for the content offset of the platform scroll view.

* WebKitTestRunner/TestController.cpp:
(WTR::updateTestOptionsFromTestHeader):
* WebKitTestRunner/TestOptions.h:
(WTR::TestOptions::hasSameInitializationOptions const):
* WebKitTestRunner/ios/TestControllerIOS.mm:
(WTR::TestController::platformResetStateToConsistentValues):
* WebKitTestRunner/ios/UIScriptControllerIOS.mm:
(WTR::UIScriptController::contentOffsetX const):
(WTR::UIScriptController::contentOffsetY const):

LayoutTests:

* editing/selection/ios/autoscroll-with-top-content-inset-expected.txt: Added.
* editing/selection/ios/autoscroll-with-top-content-inset.html: Added.

Add a new test to verify that moving the selection by autoscrolling near the top content inset area does not
cause the scroll view's content offset to jump.

* fast/scrolling/ios/scroll-into-view-with-top-content-inset-expected.txt: Added.
* fast/scrolling/ios/scroll-into-view-with-top-content-inset.html: Added.

Add a new test to verify that programmatically scrolling an element that's already visible into view does not
scroll away the scroll view's content inset.

* resources/ui-helper.js:
(window.UIHelper.contentOffset):
(window.UIHelper):

git-svn-id: https://svn.webkit.org/repository/webkit/trunk@240139 268f45cc-cd09-0410-ab3c-d52691b4dbfc
27 files changed:
LayoutTests/ChangeLog
LayoutTests/editing/selection/ios/autoscroll-with-top-content-inset-expected.txt [new file with mode: 0644]
LayoutTests/editing/selection/ios/autoscroll-with-top-content-inset.html [new file with mode: 0644]
LayoutTests/fast/scrolling/ios/scroll-into-view-with-top-content-inset-expected.txt [new file with mode: 0644]
LayoutTests/fast/scrolling/ios/scroll-into-view-with-top-content-inset.html [new file with mode: 0644]
LayoutTests/resources/ui-helper.js
Source/WebCore/ChangeLog
Source/WebCore/page/FrameView.cpp
Source/WebCore/page/FrameView.h
Source/WebCore/page/Page.h
Source/WebCore/rendering/RenderLayer.cpp
Source/WebKit/ChangeLog
Source/WebKit/Shared/VisibleContentRectUpdateInfo.cpp
Source/WebKit/Shared/VisibleContentRectUpdateInfo.h
Source/WebKit/UIProcess/API/Cocoa/WKWebView.mm
Source/WebKit/UIProcess/ios/WKContentView.h
Source/WebKit/UIProcess/ios/WKContentView.mm
Source/WebKit/WebProcess/WebPage/ios/WebPageIOS.mm
Tools/ChangeLog
Tools/DumpRenderTree/ios/UIScriptControllerIOS.mm
Tools/TestRunnerShared/UIScriptContext/Bindings/UIScriptController.idl
Tools/TestRunnerShared/UIScriptContext/UIScriptController.cpp
Tools/TestRunnerShared/UIScriptContext/UIScriptController.h
Tools/WebKitTestRunner/TestController.cpp
Tools/WebKitTestRunner/TestOptions.h
Tools/WebKitTestRunner/ios/TestControllerIOS.mm
Tools/WebKitTestRunner/ios/UIScriptControllerIOS.mm