Changeset 239314 in webkit


Ignore:
Timestamp:
Dec 17, 2018 8:04:44 PM (5 years ago)
Author:
Wenson Hsieh
Message:

[iOS] Focusing a large editable element always scrolls to the top of the element
https://bugs.webkit.org/show_bug.cgi?id=192745
<rdar://problem/46758445>

Reviewed by Tim Horton.

Source/WebKit:

Currently, when focusing form controls or editable elements, we try to scroll such that the focused element rect
is centered within the visible area. In the case of very large focusable elements whose dimensions exceed the
width or height of the visible area, we instead scroll such that the top left point of the element is at the top
left corner of the visible area.

However, this results in unnecessary scrolling if the top of the element is already near the top of the visible
area. For WebKit2-based rich text editors that have an editable body element with a top content inset that
contains additional content, this means we will always scroll the additional content away when focusing the
editable body.

To avoid this behavior, adjust focused element zooming logic for editable elements that are too large to be
centered in the visible area, such that we only scroll the top left position of the focused element to the top
half or top right of the visible area, respectively. This reduces the amount of scrolling when focusing large
editable elements, while still making it clear which element is being focused.

  • Platform/spi/ios/UIKitSPI.h:
  • UIProcess/API/Cocoa/WKWebView.mm:

(-[WKWebView _zoomToFocusRect:selectionRect:insideFixed:fontSize:minimumScale:maximumScale:allowScaling:forceScroll:]):

Make some small adjustments to improve the readability of this method by using clampTo instead of clamping
values by comparing and setting values.

Also, fix an existing bug wherein focusable elements that are meant to be centered within the visible area are
currently offset by half the difference between the bottom inset amount and the top inset amount, in the case
where the _obscuredInsets SPI is used to specify content insets for the web view (i.e., MobileSafari).

  • UIProcess/API/Cocoa/WKWebViewInternal.h:

Make a couple of arguments const FloatRect& instead of just FloatRect.

LayoutTests:

Add a new layout test to verify that we don't scroll unnecessarily when focusing a tall editable element, whose
top offset is already near the top of the viewport.

  • editing/selection/ios/no-scrolling-when-focusing-large-editable-area-expected.txt: Added.
  • editing/selection/ios/no-scrolling-when-focusing-large-editable-area.html: Added.
Location:
trunk
Files:
2 added
5 edited

Legend:

Unmodified
Added
Removed
  • trunk/LayoutTests/ChangeLog

    r239313 r239314  
     12018-12-17  Wenson Hsieh  <wenson_hsieh@apple.com>
     2
     3        [iOS] Focusing a large editable element always scrolls to the top of the element
     4        https://bugs.webkit.org/show_bug.cgi?id=192745
     5        <rdar://problem/46758445>
     6
     7        Reviewed by Tim Horton.
     8
     9        Add a new layout test to verify that we don't scroll unnecessarily when focusing a tall editable element, whose
     10        top offset is already near the top of the viewport.
     11
     12        * editing/selection/ios/no-scrolling-when-focusing-large-editable-area-expected.txt: Added.
     13        * editing/selection/ios/no-scrolling-when-focusing-large-editable-area.html: Added.
     14
    1152018-12-17  Ryosuke Niwa  <rniwa@webkit.org>
    216
  • trunk/Source/WebKit/ChangeLog

    r239313 r239314  
     12018-12-17  Wenson Hsieh  <wenson_hsieh@apple.com>
     2
     3        [iOS] Focusing a large editable element always scrolls to the top of the element
     4        https://bugs.webkit.org/show_bug.cgi?id=192745
     5        <rdar://problem/46758445>
     6
     7        Reviewed by Tim Horton.
     8
     9        Currently, when focusing form controls or editable elements, we try to scroll such that the focused element rect
     10        is centered within the visible area. In the case of very large focusable elements whose dimensions exceed the
     11        width or height of the visible area, we instead scroll such that the top left point of the element is at the top
     12        left corner of the visible area.
     13
     14        However, this results in unnecessary scrolling if the top of the element is already near the top of the visible
     15        area. For WebKit2-based rich text editors that have an editable body element with a top content inset that
     16        contains additional content, this means we will always scroll the additional content away when focusing the
     17        editable body.
     18
     19        To avoid this behavior, adjust focused element zooming logic for editable elements that are too large to be
     20        centered in the visible area, such that we only scroll the top left position of the focused element to the top
     21        half or top right of the visible area, respectively. This reduces the amount of scrolling when focusing large
     22        editable elements, while still making it clear which element is being focused.
     23
     24        * Platform/spi/ios/UIKitSPI.h:
     25        * UIProcess/API/Cocoa/WKWebView.mm:
     26        (-[WKWebView _zoomToFocusRect:selectionRect:insideFixed:fontSize:minimumScale:maximumScale:allowScaling:forceScroll:]):
     27
     28        Make some small adjustments to improve the readability of this method by using `clampTo` instead of clamping
     29        values by comparing and setting values.
     30
     31        Also, fix an existing bug wherein focusable elements that are meant to be centered within the visible area are
     32        currently offset by half the difference between the bottom inset amount and the top inset amount, in the case
     33        where the `_obscuredInsets` SPI is used to specify content insets for the web view (i.e., MobileSafari).
     34
     35        * UIProcess/API/Cocoa/WKWebViewInternal.h:
     36
     37        Make a couple of arguments `const FloatRect&` instead of just `FloatRect`.
     38
    1392018-12-17  Ryosuke Niwa  <rniwa@webkit.org>
    240
  • trunk/Source/WebKit/Platform/spi/ios/UIKitSPI.h

    r239059 r239314  
    349349@property (nonatomic, getter=_indicatorInsetAdjustmentBehavior, setter=_setIndicatorInsetAdjustmentBehavior:) UIScrollViewIndicatorInsetAdjustmentBehavior indicatorInsetAdjustmentBehavior;
    350350@property (nonatomic, readonly) UIEdgeInsets _systemContentInset;
     351@property (nonatomic, readonly) UIEdgeInsets _effectiveContentInset;
    351352@end
    352353
  • trunk/Source/WebKit/UIProcess/API/Cocoa/WKWebView.mm

    r239310 r239314  
    22262226
    22272227// focusedElementRect and selectionRect are both in document coordinates.
    2228 - (void)_zoomToFocusRect:(WebCore::FloatRect)focusedElementRectInDocumentCoordinates selectionRect:(WebCore::FloatRect)selectionRectInDocumentCoordinates insideFixed:(BOOL)insideFixed
     2228- (void)_zoomToFocusRect:(const WebCore::FloatRect&)focusedElementRectInDocumentCoordinates selectionRect:(const WebCore::FloatRect&)selectionRectInDocumentCoordinates insideFixed:(BOOL)insideFixed
    22292229    fontSize:(float)fontSize minimumScale:(double)minimumScale maximumScale:(double)maximumScale allowScaling:(BOOL)allowScaling forceScroll:(BOOL)forceScroll
    22302230{
     
    23062306    }
    23072307
    2308     // We want to zoom to the left/top corner of the DOM node, with as much spacing on all sides as we
    2309     // can get based on the visible area after zooming (workingFrame). The spacing in either dimension is half the
     2308    // We want to center the focused element within the viewport, with as much spacing on all sides as
     2309    // we can get based on the visible area after zooming. The spacing in either dimension is half the
    23102310    // difference between the size of the DOM node and the size of the visible frame.
    2311     CGFloat horizontalSpaceInWebViewCoordinates = std::max((visibleSize.width - focusedElementRectInNewScale.width()) / 2.0, 0.0);
    2312     CGFloat verticalSpaceInWebViewCoordinates = std::max((visibleSize.height - focusedElementRectInNewScale.height()) / 2.0, 0.0);
    2313 
    2314     CGPoint topLeft;
    2315     topLeft.x = focusedElementRectInNewScale.x() - horizontalSpaceInWebViewCoordinates;
    2316     topLeft.y = focusedElementRectInNewScale.y() - verticalSpaceInWebViewCoordinates - visibleOffsetFromTop;
     2311    // If the element is too wide to be horizontally centered or too tall to be vertically centered, we
     2312    // instead scroll such that the left edge or top edge of the element is within the left half or top
     2313    // half of the viewport, respectively.
     2314    CGFloat horizontalSpaceInWebViewCoordinates = (visibleSize.width - focusedElementRectInNewScale.width()) / 2.0;
     2315    CGFloat verticalSpaceInWebViewCoordinates = (visibleSize.height - focusedElementRectInNewScale.height()) / 2.0;
     2316
     2317    auto topLeft = CGPointZero;
     2318    auto scrollViewInsets = [_scrollView _effectiveContentInset];
     2319    auto currentTopLeft = [_scrollView contentOffset];
     2320
     2321    if (_haveSetObscuredInsets) {
     2322        currentTopLeft.x += _obscuredInsets.left;
     2323        currentTopLeft.y += _obscuredInsets.top;
     2324    }
     2325
     2326    if (horizontalSpaceInWebViewCoordinates > 0)
     2327        topLeft.x = focusedElementRectInNewScale.x() - horizontalSpaceInWebViewCoordinates;
     2328    else {
     2329        auto minimumOffsetToRevealLeftEdge = std::max(-scrollViewInsets.left, focusedElementRectInNewScale.x() - visibleSize.width / 2);
     2330        auto maximumOffsetToRevealLeftEdge = focusedElementRectInNewScale.x();
     2331        topLeft.x = clampTo<double>(currentTopLeft.x, minimumOffsetToRevealLeftEdge, maximumOffsetToRevealLeftEdge);
     2332    }
     2333
     2334    if (verticalSpaceInWebViewCoordinates > 0)
     2335        topLeft.y = focusedElementRectInNewScale.y() - verticalSpaceInWebViewCoordinates;
     2336    else {
     2337        auto minimumOffsetToRevealTopEdge = std::max(-scrollViewInsets.top, focusedElementRectInNewScale.y() - visibleSize.height / 2);
     2338        auto maximumOffsetToRevealTopEdge = focusedElementRectInNewScale.y();
     2339        topLeft.y = clampTo<double>(currentTopLeft.y, minimumOffsetToRevealTopEdge, maximumOffsetToRevealTopEdge);
     2340    }
     2341
     2342    topLeft.y -= visibleOffsetFromTop;
     2343
     2344    WebCore::FloatRect documentBoundsInNewScale = [_contentView bounds];
     2345    documentBoundsInNewScale.scale(scale);
     2346    documentBoundsInNewScale.moveBy([_contentView frame].origin);
    23172347
    23182348    CGFloat minimumAllowableHorizontalOffsetInWebViewCoordinates = -INFINITY;
    23192349    CGFloat minimumAllowableVerticalOffsetInWebViewCoordinates = -INFINITY;
     2350    CGFloat maximumAllowableHorizontalOffsetInWebViewCoordinates = CGRectGetMaxX(documentBoundsInNewScale) - visibleSize.width;
     2351    CGFloat maximumAllowableVerticalOffsetInWebViewCoordinates = CGRectGetMaxY(documentBoundsInNewScale) - visibleSize.height;
     2352
    23202353    if (selectionRectIsNotNull) {
    23212354        WebCore::FloatRect selectionRectInNewScale = selectionRectInDocumentCoordinates;
    23222355        selectionRectInNewScale.scale(scale);
    23232356        selectionRectInNewScale.moveBy([_contentView frame].origin);
     2357        // Adjust the min and max allowable scroll offsets, such that the selection rect remains visible.
    23242358        minimumAllowableHorizontalOffsetInWebViewCoordinates = CGRectGetMaxX(selectionRectInNewScale) + caretOffsetFromWindowEdge - visibleSize.width;
    23252359        minimumAllowableVerticalOffsetInWebViewCoordinates = CGRectGetMaxY(selectionRectInNewScale) + caretOffsetFromWindowEdge - visibleSize.height - visibleOffsetFromTop;
    2326     }
    2327 
    2328     WebCore::FloatRect documentBoundsInNewScale = [_contentView bounds];
    2329     documentBoundsInNewScale.scale(scale);
    2330     documentBoundsInNewScale.moveBy([_contentView frame].origin);
     2360        maximumAllowableHorizontalOffsetInWebViewCoordinates = std::min(maximumAllowableHorizontalOffsetInWebViewCoordinates, CGRectGetMinX(selectionRectInNewScale) - caretOffsetFromWindowEdge);
     2361        maximumAllowableVerticalOffsetInWebViewCoordinates = std::min(maximumAllowableVerticalOffsetInWebViewCoordinates, CGRectGetMinY(selectionRectInNewScale) - caretOffsetFromWindowEdge - visibleOffsetFromTop);
     2362    }
    23312363
    23322364    // Constrain the left edge in document coordinates so that:
    23332365    //  - it isn't so small that the scrollVisibleRect isn't visible on the screen
    23342366    //  - it isn't so great that the document's right edge is less than the right edge of the screen
    2335     if (selectionRectIsNotNull && topLeft.x < minimumAllowableHorizontalOffsetInWebViewCoordinates)
    2336         topLeft.x = minimumAllowableHorizontalOffsetInWebViewCoordinates;
    2337     else {
    2338         CGFloat maximumAllowableHorizontalOffset = CGRectGetMaxX(documentBoundsInNewScale) - visibleSize.width;
    2339         if (topLeft.x > maximumAllowableHorizontalOffset)
    2340             topLeft.x = maximumAllowableHorizontalOffset;
    2341     }
     2367    topLeft.x = clampTo<CGFloat>(topLeft.x, minimumAllowableHorizontalOffsetInWebViewCoordinates, maximumAllowableHorizontalOffsetInWebViewCoordinates);
    23422368
    23432369    // Constrain the top edge in document coordinates so that:
    23442370    //  - it isn't so small that the scrollVisibleRect isn't visible on the screen
    23452371    //  - it isn't so great that the document's bottom edge is higher than the top of the form assistant
    2346     if (selectionRectIsNotNull && topLeft.y < minimumAllowableVerticalOffsetInWebViewCoordinates)
    2347         topLeft.y = minimumAllowableVerticalOffsetInWebViewCoordinates;
    2348     else {
    2349         CGFloat maximumAllowableVerticalOffset = CGRectGetMaxY(documentBoundsInNewScale) - visibleSize.height;
    2350         if (topLeft.y > maximumAllowableVerticalOffset)
    2351             topLeft.y = maximumAllowableVerticalOffset;
    2352     }
    2353 
    2354     WebCore::FloatPoint newCenter = CGPointMake(topLeft.x + unobscuredScrollViewRectInWebViewCoordinates.size.width / 2.0, topLeft.y + unobscuredScrollViewRectInWebViewCoordinates.size.height / 2.0);
     2372    topLeft.y = clampTo<CGFloat>(topLeft.y, minimumAllowableVerticalOffsetInWebViewCoordinates, maximumAllowableVerticalOffsetInWebViewCoordinates);
     2373
     2374    if (_haveSetObscuredInsets) {
     2375        // This looks unintuitive, but is necessary in order to precisely center the focused element in the visible area.
     2376        // The top left position already accounts for top and left obscured insets - i.e., a topLeft of (0, 0) corresponds
     2377        // to the top- and left-most point below (and to the right of) the top inset area and left inset areas, respectively.
     2378        // However, when telling WKScrollView to scroll to a given center position, this center position is computed relative
     2379        // to the coordinate space of the scroll view. Thus, to compute our center position from the top left position, we
     2380        // need to first move the top left position up and to the left, and then add half the width and height of the content
     2381        // area (including obscured insets).
     2382        topLeft.x -= _obscuredInsets.left;
     2383        topLeft.y -= _obscuredInsets.top;
     2384    }
     2385
     2386    WebCore::FloatPoint newCenter = CGPointMake(topLeft.x + CGRectGetWidth(self.bounds) / 2, topLeft.y + CGRectGetHeight(self.bounds) / 2);
    23552387
    23562388    if (scale != currentScale)
  • trunk/Source/WebKit/UIProcess/API/Cocoa/WKWebViewInternal.h

    r238771 r239314  
    102102- (void)_scrollToContentScrollPosition:(WebCore::FloatPoint)scrollPosition scrollOrigin:(WebCore::IntPoint)scrollOrigin;
    103103- (BOOL)_scrollToRect:(WebCore::FloatRect)targetRect origin:(WebCore::FloatPoint)origin minimumScrollDistance:(float)minimumScrollDistance;
    104 - (void)_zoomToFocusRect:(WebCore::FloatRect)focusedElementRect selectionRect:(WebCore::FloatRect)selectionRectInDocumentCoordinates insideFixed:(BOOL)insideFixed fontSize:(float)fontSize minimumScale:(double)minimumScale maximumScale:(double)maximumScale allowScaling:(BOOL)allowScaling forceScroll:(BOOL)forceScroll;
     104- (void)_zoomToFocusRect:(const WebCore::FloatRect&)focusedElementRect selectionRect:(const WebCore::FloatRect&)selectionRectInDocumentCoordinates insideFixed:(BOOL)insideFixed fontSize:(float)fontSize minimumScale:(double)minimumScale maximumScale:(double)maximumScale allowScaling:(BOOL)allowScaling forceScroll:(BOOL)forceScroll;
    105105- (BOOL)_zoomToRect:(WebCore::FloatRect)targetRect withOrigin:(WebCore::FloatPoint)origin fitEntireRect:(BOOL)fitEntireRect minimumScale:(double)minimumScale maximumScale:(double)maximumScale minimumScrollDistance:(float)minimumScrollDistance;
    106106- (void)_zoomOutWithOrigin:(WebCore::FloatPoint)origin animated:(BOOL)animated;
Note: See TracChangeset for help on using the changeset viewer.