Changeset 239441 in webkit
- Timestamp:
- Dec 20, 2018 8:28:55 AM (5 years ago)
- Location:
- trunk
- Files:
-
- 2 added
- 13 edited
Legend:
- Unmodified
- Added
- Removed
-
trunk/LayoutTests/ChangeLog
r239424 r239441 1 2018-12-20 Wenson Hsieh <wenson_hsieh@apple.com> 2 3 [iOS] Focusing an editable element should scroll to reveal the selection 4 https://bugs.webkit.org/show_bug.cgi?id=192802 5 <rdar://problem/46781759> 6 7 Reviewed by Tim Horton. 8 9 Adds a new layout test to verify that tapping near the bottom of a tall editable element to focus it doesn't 10 cause the page to scroll up (and, as a result, leave the selection caret obscured). 11 12 * editing/selection/ios/selection-is-visible-after-focusing-editable-area-expected.txt: Added. 13 * editing/selection/ios/selection-is-visible-after-focusing-editable-area.html: Added. 14 1 15 2018-12-19 Ross Kirsling <ross.kirsling@sony.com> 2 16 -
trunk/Source/WebKit/ChangeLog
r239440 r239441 1 2018-12-20 Wenson Hsieh <wenson_hsieh@apple.com> 2 3 [iOS] Focusing an editable element should scroll to reveal the selection 4 https://bugs.webkit.org/show_bug.cgi?id=192802 5 <rdar://problem/46781759> 6 7 Reviewed by Tim Horton. 8 9 Currently, when tapping on an editable element, logic in -[WKWebView _zoomToFocusRect:…:] attempts to adjust the 10 visible viewport such that the rect containing the selection is visible. However, AssistedNodeInformation's 11 selectionRect is used here, which (as the FIXME in WebPage::getAssistedNodeInformation notes) is either the last 12 touch location, or the top left of the element if the touch location is outside of the element's bounding rect. 13 This leads to confusing and undesirable behavior when tapping near the bottom of a large contenteditable element 14 to focus it, since the actual selection will end up near the top of the element, yet we'll try to scroll to 15 reveal the bottom of the element, which causes the visible selection to scroll offscreen. Notably, this affects 16 scenarios involving editable web views embedded in apps, such as Mail compose. 17 18 Right now, we use the last touch location as an approximation for the selection rect because the selection may 19 have not yet been updated at the moment when focus moves into an editable element. To fix this, we defer the 20 process of zooming to the focused element rect until after the selection changes and the UI process is updated 21 with information about the new selection rects. 22 23 Test: editing/selection/ios/selection-is-visible-after-focusing-editable-area.html 24 25 * Shared/AssistedNodeInformation.cpp: 26 (WebKit::AssistedNodeInformation::encode const): 27 (WebKit::AssistedNodeInformation::decode): 28 * Shared/AssistedNodeInformation.h: 29 30 Rename selectionRect to elementInteractionLocation, to more accurately reflect its value and purpose. This isn't 31 strictly always the last touch location, since we may default to the focused element location instead if the 32 last touch location is outside of the element rect. 33 34 * UIProcess/API/Cocoa/WKWebView.mm: 35 (-[WKWebView _zoomToFocusRect:selectionRect:insideFixed:fontSize:minimumScale:maximumScale:allowScaling:forceScroll:]): 36 37 Tweak a constant that determines the minimum amount of margin to leave between the selection rect and the edge 38 of the window when scrolling to reveal the focused element. Previously, this was larger than necessary to 39 accomodate for the fact that the "selection rect" used when zooming to the focused element did not take the 40 actual selection into account at all, and was simply a 1 by 1 rect; this meant that the margin needed to be 41 large enough to exceed the usual height of a text caret in editable content. Since we now use the real selection 42 rect, we can be much less generous with this margin. 43 44 * UIProcess/ios/WKContentViewInteraction.h: 45 * UIProcess/ios/WKContentViewInteraction.mm: 46 (-[WKContentView cleanupInteraction]): 47 (-[WKContentView observeValueForKeyPath:ofObject:change:context:]): 48 49 Don't additionally update the selection in the middle of triggering zooming to the focused element; on 50 particular versions of iOS, this now attempts to scroll the selection rect on-screen, which then conflicts with 51 zooming to reveal the focused element. 52 53 (-[WKContentView _zoomToRevealFocusedElement]): 54 55 Renamed from _displayFormNodeInputView to _zoomToRevealFocusedElement, to make the purpose of this function more 56 clear. Additionally, pull logic to update the accessory view out of this method, so that it's strictly concerned 57 with zooming to the focused element. 58 59 (-[WKContentView inputView]): 60 61 Add a FIXME describing the implications of zooming to the focused element in the implementation of -inputView. 62 See also: <https://bugs.webkit.org/show_bug.cgi?id=192878>. 63 64 (-[WKContentView accessoryTab:]): 65 66 Fix a subtle issue when keeping track of _didAccessoryTabInitiateFocus. Currently, this is set to YES in 67 -accessoryTab: and unset in _displayFormNodeInputView, but since _displayFormNodeInputView may be invoked 68 multiple times for the same focused element (see: -inputView), we might end up zooming to the focused element 69 with _didAccessoryTabInitiateFocus set to NO, even though we initiated focus with the previous/next buttons. 70 71 Instead, temporarily set a different ivar, _isChangingFocusUsingAccessoryTab, to YES in -accessoryTab:, and 72 unset it in the completion handler after the focused element has changed. Then, when we _startAssistingNode:, 73 set _didAccessoryTabInitiateFocus to _isChangingFocusUsingAccessoryTab. This ensures that the correctness of 74 _didAccessoryTabInitiateFocus isn't tied to the number of times -[WKContentView inputView] is invoked when 75 focusing an element. 76 77 (shouldZoomToRevealSelectionRect): 78 (rectToRevealWhenZoomingToFocusedElement): 79 80 Add a helper method to determine the selection rect to use when zooming to reveal the focused element. ASSERTs 81 that we have post-layout data in the EditorState. 82 83 (-[WKContentView _startAssistingNode:userIsInteracting:blurPreviousNode:changingActivityState:userObject:]): 84 85 When "assisting" a focused element, immediately zoom to it if we don't need selection information to compute the 86 rect to zoom to; otherwise, defer zooming until we receive the first editor state update in the UI process that 87 contains information about our selection rects. 88 89 (-[WKContentView _stopAssistingNode]): 90 (-[WKContentView _didReceiveEditorStateUpdateAfterFocus]): 91 92 If necessary, reveal the focused element by zooming. 93 94 (-[WKContentView _updateInitialWritingDirectionIfNecessary]): 95 96 Pull this initial writing direction update logic out into a separate helper method. 97 98 (-[WKContentView _displayFormNodeInputView]): Deleted. 99 100 Replaced by _zoomToRevealFocusedElement. 101 102 * WebProcess/WebCoreSupport/WebChromeClient.cpp: 103 (WebKit::WebChromeClient::elementDidRefocus): 104 105 This currently calls WebChromeClient::elementDidFocus; instead, call the new WebPage::elementDidRefocus; 106 additionally, make this available on all PLATFORM(COCOA), rather than just IOS_FAMILY. 107 108 * WebProcess/WebCoreSupport/WebChromeClient.h: 109 * WebProcess/WebCoreSupport/ios/WebChromeClientIOS.mm: 110 (WebKit::WebChromeClient::elementDidRefocus): Deleted. 111 112 Replaced by the PLATFORM(COCOA) version. 113 114 * WebProcess/WebPage/WebPage.cpp: 115 (WebKit::WebPage::elementDidRefocus): 116 117 When refocusing an element, ensure that post-layout editor state data is sent to the UI process by including a 118 full EditorState in the next layer tree transaction. 119 120 * WebProcess/WebPage/WebPage.h: 121 * WebProcess/WebPage/ios/WebPageIOS.mm: 122 (WebKit::WebPage::completeSyntheticClick): 123 124 Call elementDidRefocus instead of elementDidFocus, in the case where the existing focused element is clicked. 125 126 (WebKit::WebPage::getAssistedNodeInformation): 127 128 Adjust for the change from selectionRect to elementInteractionLocation. 129 1 130 2018-12-20 Patrick Griffis <pgriffis@igalia.com> 2 131 -
trunk/Source/WebKit/Shared/AssistedNodeInformation.cpp
r239427 r239441 65 65 { 66 66 encoder << elementRect; 67 encoder << selectionRect;67 encoder << elementInteractionLocation; 68 68 encoder << minimumScaleFactor; 69 69 encoder << maximumScaleFactor; … … 113 113 return false; 114 114 115 if (!decoder.decode(result. selectionRect))115 if (!decoder.decode(result.elementInteractionLocation)) 116 116 return false; 117 117 -
trunk/Source/WebKit/Shared/AssistedNodeInformation.h
r239427 r239441 96 96 struct AssistedNodeInformation { 97 97 WebCore::IntRect elementRect; 98 WebCore::Int Rect selectionRect;98 WebCore::IntPoint elementInteractionLocation; 99 99 double minimumScaleFactor { -INFINITY }; 100 100 double maximumScaleFactor { INFINITY }; -
trunk/Source/WebKit/UIProcess/API/Cocoa/WKWebView.mm
r239427 r239441 2248 2248 const double minimumHeightToShowContentAboveKeyboard = 106; 2249 2249 const CFTimeInterval formControlZoomAnimationDuration = 0.25; 2250 const double caretOffsetFromWindowEdge = 20;2250 const double caretOffsetFromWindowEdge = 8; 2251 2251 2252 2252 UIWindow *window = [_scrollView window]; -
trunk/Source/WebKit/UIProcess/ios/WKContentViewInteraction.h
r239427 r239441 302 302 BOOL _usingGestureForSelection; 303 303 BOOL _inspectorNodeSearchEnabled; 304 BOOL _isChangingFocusUsingAccessoryTab; 304 305 BOOL _didAccessoryTabInitiateFocus; 305 306 BOOL _isExpectingFastSingleTapCommit; 306 307 BOOL _showDebugTapHighlightsForFastClicking; 308 BOOL _isZoomingToRevealFocusedElement; 307 309 308 310 BOOL _becomingFirstResponder; -
trunk/Source/WebKit/UIProcess/ios/WKContentViewInteraction.mm
r239427 r239441 758 758 _smartMagnificationController = nil; 759 759 _didAccessoryTabInitiateFocus = NO; 760 _isChangingFocusUsingAccessoryTab = NO; 760 761 _isExpectingFastSingleTapCommit = NO; 761 762 _needsDeferredEndScrollingSelectionUpdate = NO; … … 855 856 _hasSetUpInteractions = NO; 856 857 _suppressSelectionAssistantReasons = { }; 858 _isZoomingToRevealFocusedElement = NO; 857 859 } 858 860 … … 942 944 } 943 945 946 [self _updateTapHighlight]; 947 948 if (_isZoomingToRevealFocusedElement) { 949 // When zooming to the focused element, avoid additionally scrolling to reveal the selection. Since the scale transform has not yet been 950 // applied to the content view at this point, we'll end up scrolling to reveal a rect that is computed using the wrong scale. 951 return; 952 } 953 944 954 _selectionNeedsUpdate = YES; 945 955 [self _updateChangedSelection:YES]; 946 [self _updateTapHighlight];947 956 } 948 957 … … 1388 1397 } 1389 1398 1390 - (void)_displayFormNodeInputView 1391 { 1392 if (!_suppressSelectionAssistantReasons.contains(WebKit::FocusedElementIsTransparent)) { 1393 // In case user scaling is force enabled, do not use that scaling when zooming in with an input field. 1394 // Zooming above the page's default scale factor should only happen when the user performs it. 1395 [self _zoomToFocusRect:_assistedNodeInformation.elementRect 1396 selectionRect:_didAccessoryTabInitiateFocus ? WebCore::IntRect() : _assistedNodeInformation.selectionRect 1397 insideFixed:_assistedNodeInformation.insideFixedPosition 1398 fontSize:_assistedNodeInformation.nodeFontSize 1399 minimumScale:_assistedNodeInformation.minimumScaleFactor 1400 maximumScale:_assistedNodeInformation.maximumScaleFactorIgnoringAlwaysScalable 1401 allowScaling:_assistedNodeInformation.allowsUserScalingIgnoringAlwaysScalable && !currentUserInterfaceIdiomIsPad() 1402 forceScroll:(_assistedNodeInformation.inputMode == WebCore::InputMode::None) ? !currentUserInterfaceIdiomIsPad() : [self requiresAccessoryView]]; 1403 } 1404 1405 _didAccessoryTabInitiateFocus = NO; 1406 [self _ensureFormAccessoryView]; 1407 [self _updateAccessory]; 1399 - (void)_zoomToRevealFocusedElement 1400 { 1401 if (_suppressSelectionAssistantReasons.contains(WebKit::FocusedElementIsTransparent)) 1402 return; 1403 1404 SetForScope<BOOL> isZoomingToRevealFocusedElementForScope { _isZoomingToRevealFocusedElement, YES }; 1405 // In case user scaling is force enabled, do not use that scaling when zooming in with an input field. 1406 // Zooming above the page's default scale factor should only happen when the user performs it. 1407 [self _zoomToFocusRect:_assistedNodeInformation.elementRect 1408 selectionRect:_didAccessoryTabInitiateFocus ? WebCore::FloatRect() : rectToRevealWhenZoomingToFocusedElement(_assistedNodeInformation, _page->editorState()) 1409 insideFixed:_assistedNodeInformation.insideFixedPosition 1410 fontSize:_assistedNodeInformation.nodeFontSize 1411 minimumScale:_assistedNodeInformation.minimumScaleFactor 1412 maximumScale:_assistedNodeInformation.maximumScaleFactorIgnoringAlwaysScalable 1413 allowScaling:_assistedNodeInformation.allowsUserScalingIgnoringAlwaysScalable && !currentUserInterfaceIdiomIsPad() 1414 forceScroll:(_assistedNodeInformation.inputMode == WebCore::InputMode::None) ? !currentUserInterfaceIdiomIsPad() : [self requiresAccessoryView]]; 1408 1415 } 1409 1416 … … 1430 1437 break; 1431 1438 } 1432 } else 1433 [self _displayFormNodeInputView]; 1439 } else { 1440 // FIXME: UIKit may invoke -[WKContentView inputView] at any time when WKContentView is the first responder; 1441 // as such, it doesn't make sense to change the enclosing scroll view's zoom scale and content offset to reveal 1442 // the focused element here. It seems this behavior was added to match logic in legacy WebKit (refer to 1443 // UIWebBrowserView). Instead, we should find the places where we currently assume that UIKit (or other clients) 1444 // invoke -inputView to zoom to the focused element, and either surface SPI for clients to zoom to the focused 1445 // element, or explicitly trigger the zoom from WebKit. 1446 // For instance, one use case that currently relies on this detail is adjusting the zoom scale and viewport upon 1447 // rotation, when a select element is focused. See <https://webkit.org/b/192878> for more information. 1448 [self _zoomToRevealFocusedElement]; 1449 [self _ensureFormAccessoryView]; 1450 [self _updateAccessory]; 1451 } 1434 1452 1435 1453 if (UIView *customInputView = [_formInputSession customInputView]) … … 3331 3349 _inputPeripheral = nil; 3332 3350 3333 _ didAccessoryTabInitiateFocus = YES; // Will be cleared in either -_displayFormNodeInputView or -cleanupInteraction.3351 _isChangingFocusUsingAccessoryTab = YES; 3334 3352 [self beginSelectionChange]; 3335 3353 RetainPtr<WKContentView> view = self; … … 3337 3355 [view endSelectionChange]; 3338 3356 [view reloadInputViews]; 3357 view->_isChangingFocusUsingAccessoryTab = NO; 3339 3358 }); 3340 3359 } … … 4349 4368 } 4350 4369 4370 static bool shouldZoomToRevealSelectionRect(WebKit::InputType type) 4371 { 4372 switch (type) { 4373 case WebKit::InputType::ContentEditable: 4374 case WebKit::InputType::Text: 4375 case WebKit::InputType::Password: 4376 case WebKit::InputType::TextArea: 4377 case WebKit::InputType::Search: 4378 case WebKit::InputType::Email: 4379 case WebKit::InputType::URL: 4380 case WebKit::InputType::Phone: 4381 case WebKit::InputType::Number: 4382 case WebKit::InputType::NumberPad: 4383 return true; 4384 default: 4385 return false; 4386 } 4387 } 4388 4389 static WebCore::FloatRect rectToRevealWhenZoomingToFocusedElement(const WebKit::AssistedNodeInformation& elementInfo, const WebKit::EditorState& editorState) 4390 { 4391 WebCore::IntRect elementInteractionRect(elementInfo.elementInteractionLocation, { 1, 1 }); 4392 if (!shouldZoomToRevealSelectionRect(elementInfo.elementType)) 4393 return elementInteractionRect; 4394 4395 if (editorState.isMissingPostLayoutData) { 4396 ASSERT_NOT_REACHED(); 4397 return elementInteractionRect; 4398 } 4399 4400 if (editorState.selectionIsNone) 4401 return { }; 4402 4403 WebCore::FloatRect selectionBoundingRect; 4404 auto& postLayoutData = editorState.postLayoutData(); 4405 if (editorState.selectionIsRange) { 4406 for (auto& rect : postLayoutData.selectionRects) 4407 selectionBoundingRect.unite(rect.rect()); 4408 } else 4409 selectionBoundingRect = postLayoutData.caretRectAtStart; 4410 4411 selectionBoundingRect.intersect(elementInfo.elementRect); 4412 return selectionBoundingRect; 4413 } 4414 4351 4415 static bool isAssistableInputType(WebKit::InputType type) 4352 4416 { … … 4388 4452 _inputViewUpdateDeferrer = nullptr; 4389 4453 4454 _didAccessoryTabInitiateFocus = _isChangingFocusUsingAccessoryTab; 4455 4390 4456 id <_WKInputDelegate> inputDelegate = [_webView _inputDelegate]; 4391 4457 RetainPtr<WKFocusedElementInfo> focusedElementInfo = adoptNS([[WKFocusedElementInfo alloc] initWithAssistedNodeInformation:information isUserInitiated:userIsInteracting userObject:userObject]); … … 4507 4573 [_webView _scheduleVisibleContentRectUpdate]; 4508 4574 4509 [self _displayFormNodeInputView]; 4575 if (!shouldZoomToRevealSelectionRect(_assistedNodeInformation.elementType)) 4576 [self _zoomToRevealFocusedElement]; 4577 4578 [self _ensureFormAccessoryView]; 4579 [self _updateAccessory]; 4510 4580 4511 4581 #if PLATFORM(WATCHOS) … … 4564 4634 4565 4635 [self _stopSuppressingSelectionAssistantForReason:WebKit::FocusedElementIsTransparent]; 4636 4637 if (!_isChangingFocus) 4638 _didAccessoryTabInitiateFocus = NO; 4566 4639 } 4567 4640 4568 4641 - (void)_didReceiveEditorStateUpdateAfterFocus 4642 { 4643 [self _updateInitialWritingDirectionIfNecessary]; 4644 4645 // FIXME: If the initial writing direction just changed, we should wait until we get the next post-layout editor state 4646 // before zooming to reveal the selection rect. 4647 if (shouldZoomToRevealSelectionRect(_assistedNodeInformation.elementType)) 4648 [self _zoomToRevealFocusedElement]; 4649 } 4650 4651 - (void)_updateInitialWritingDirectionIfNecessary 4569 4652 { 4570 4653 if (!_page->isEditable()) -
trunk/Source/WebKit/WebProcess/WebCoreSupport/WebChromeClient.cpp
r239427 r239441 204 204 } 205 205 206 void WebChromeClient::elementDidRefocus(Element& element) 207 { 208 m_page.elementDidRefocus(&element); 209 } 210 206 211 void WebChromeClient::elementDidBlur(Element& element) 207 212 { -
trunk/Source/WebKit/WebProcess/WebCoreSupport/WebChromeClient.h
r239427 r239441 253 253 #endif 254 254 255 #if PLATFORM(IOS_FAMILY)256 void elementDidRefocus(WebCore::Element&) final;257 #endif258 259 255 #if (PLATFORM(IOS_FAMILY) && HAVE(AVKIT)) || (PLATFORM(MAC) && ENABLE(VIDEO_PRESENTATION_MODE)) 260 256 bool supportsVideoFullscreen(WebCore::HTMLMediaElementEnums::VideoFullscreenMode) final; … … 279 275 void elementDidFocus(WebCore::Element&) final; 280 276 void elementDidBlur(WebCore::Element&) final; 277 void elementDidRefocus(WebCore::Element&) final; 281 278 282 279 void makeFirstResponder() final; -
trunk/Source/WebKit/WebProcess/WebCoreSupport/ios/WebChromeClientIOS.mm
r238538 r239441 53 53 54 54 #endif 55 56 void WebChromeClient::elementDidRefocus(WebCore::Element& element)57 {58 elementDidFocus(element);59 }60 55 61 56 void WebChromeClient::didReceiveMobileDocType(bool isMobileDoctype) -
trunk/Source/WebKit/WebProcess/WebPage/WebPage.cpp
r239427 r239441 5279 5279 } 5280 5280 5281 void WebPage::elementDidRefocus(WebCore::Node* node) 5282 { 5283 elementDidFocus(node); 5284 m_hasPendingEditorStateUpdate = true; 5285 } 5286 5281 5287 void WebPage::elementDidFocus(WebCore::Node* node) 5282 5288 { -
trunk/Source/WebKit/WebProcess/WebPage/WebPage.h
r239427 r239441 586 586 #endif 587 587 588 // FIXME: These should all take Element& instead of Node*. 588 589 void elementDidFocus(WebCore::Node*); 590 void elementDidRefocus(WebCore::Node*); 589 591 void elementDidBlur(WebCore::Node*); 590 592 void resetAssistedNodeForFrame(WebFrame*); -
trunk/Source/WebKit/WebProcess/WebPage/ios/WebPageIOS.mm
r239427 r239441 616 616 // keyboard is not on screen. 617 617 if (newFocusedElement && newFocusedElement == oldFocusedElement) 618 elementDid Focus(newFocusedElement.get());618 elementDidRefocus(newFocusedElement.get()); 619 619 620 620 if (!tapWasHandled || !nodeRespondingToClick || !nodeRespondingToClick->isElementNode()) … … 2364 2364 layoutIfNeeded(); 2365 2365 2366 // FIXME: information.selectionRect should be set to the actual selection rect, but when this is called at focus time 2367 // we don't have a selection yet. Using the last interaction location is a reasonable approximation for now. 2368 information.selectionRect = IntRect(m_lastInteractionLocation, IntSize(1, 1)); 2366 information.elementInteractionLocation = m_lastInteractionLocation; 2369 2367 2370 2368 if (RenderObject* renderer = m_assistedNode->renderer()) { … … 2387 2385 2388 2386 if (!information.elementRect.contains(m_lastInteractionLocation)) 2389 information. selectionRect.setLocation(information.elementRect.location());2387 information.elementInteractionLocation = information.elementRect.location(); 2390 2388 } else { 2391 2389 // Don't use the selection rect if interaction was outside the element rect. 2392 2390 if (!information.elementRect.contains(m_lastInteractionLocation)) 2393 information. selectionRect = IntRect();2391 information.elementInteractionLocation = { }; 2394 2392 } 2395 2393 } else
Note: See TracChangeset
for help on using the changeset viewer.