Changeset 259550 in webkit
- Timestamp:
- Apr 5, 2020 9:34:39 AM (4 years ago)
- Location:
- trunk/Source/WebCore
- Files:
-
- 3 edited
Legend:
- Unmodified
- Added
- Removed
-
trunk/Source/WebCore/ChangeLog
r259544 r259550 1 2020-04-05 Wenson Hsieh <wenson_hsieh@apple.com> 2 3 [iOS] Ugly and misaligned form validation bubble 4 https://bugs.webkit.org/show_bug.cgi?id=208472 5 <rdar://problem/59984027> 6 7 Reviewed by Tim Horton. 8 9 In iOS 13, the view of a `UIViewController` that is presented as a popover encompasses the arrow (connected to 10 the popover) that points to the target rect. This means that our current logic for laying out the inner text 11 label of a form validation bubble on iOS no longer works, since it sets a frame that is offset vertically and 12 horizontally from the bounds of the view controller's view. 13 14 To fix this, we need to respect the safe area insets of the view controller's view when laying out the label. 15 The idiomatic way to do this is to subclass -viewSafeAreaInsetsDidChange and -viewWillLayoutSubviews on the view 16 controller, and update the subview's (i.e. label's) frame; unfortunately, since ValidationBubble is implemented 17 in WebCore, we can't explicitly link against UIKit, so we need to dynamically create a UIViewController subclass 18 and override these subclassing hooks to get our desired behavior. 19 20 * platform/ValidationBubble.h: 21 * platform/ios/ValidationBubbleIOS.mm: 22 (invokeUIViewControllerSelector): 23 (WebValidationBubbleViewController_dealloc): 24 (WebValidationBubbleViewController_viewDidLoad): 25 (WebValidationBubbleViewController_viewWillLayoutSubviews): 26 (WebValidationBubbleViewController_viewSafeAreaInsetsDidChange): 27 (WebValidationBubbleViewController_labelFrame): 28 (WebValidationBubbleViewController_label): 29 (allocWebValidationBubbleViewControllerInstance): 30 31 Subclass and create a custom UIViewController to ensure that the label is vertically centered in its popover. 32 See above for more details. 33 34 (WebCore::ValidationBubble::ValidationBubble): 35 (WebCore::ValidationBubble::show): 36 37 Minor style fixes: remove extraneous `.get()`s on `RetainPtr`, and use property syntax when possible. 38 39 (WebCore::ValidationBubble::setAnchorRect): 40 41 Additionally remove a line of code that currently forces the form validation popover to present below its target 42 rect (and therefore have an arrow pointing up). It wasn't apparent why this logic was added in r208361, but it 43 seems the intention wasn't to restrict the popover to presenting below the target. 44 45 This allows the form validation popover to show up in the case where the input element is aligned to the very 46 bottom of the web view, such that there isn't enough space below the field to show the validation bubble. 47 1 48 2020-04-04 Rob Buis <rbuis@igalia.com> 2 49 -
trunk/Source/WebCore/platform/ValidationBubble.h
r237266 r259550 40 40 OBJC_CLASS WebValidationBubbleDelegate; 41 41 OBJC_CLASS WebValidationBubbleTapRecognizer; 42 OBJC_CLASS WebValidationBubbleViewController; 42 43 #endif 43 44 … … 86 87 RetainPtr<NSPopover> m_popover; 87 88 #elif PLATFORM(IOS_FAMILY) 88 RetainPtr< UIViewController> m_popoverController;89 RetainPtr<WebValidationBubbleViewController> m_popoverController; 89 90 RetainPtr<WebValidationBubbleTapRecognizer> m_tapRecognizer; 90 91 RetainPtr<WebValidationBubbleDelegate> m_popoverDelegate; -
trunk/Source/WebCore/platform/ios/ValidationBubbleIOS.mm
r240494 r259550 30 30 #import "ValidationBubble.h" 31 31 32 #import <UIKit/UIGeometry.h> 33 #import <objc/message.h> 34 #import <objc/runtime.h> 32 35 #import <pal/ios/UIKitSoftLink.h> 33 36 #import <pal/spi/ios/UIKitSPI.h> … … 36 39 #import <wtf/text/WTFString.h> 37 40 41 static const CGFloat validationBubbleHorizontalPadding = 17; 42 static const CGFloat validationBubbleVerticalPadding = 9; 43 static const CGFloat validationBubbleMaxLabelWidth = 300; 44 45 @interface WebValidationBubbleViewController : UIViewController 46 @property (nonatomic, readonly) UILabel *label; 47 @property (nonatomic, readonly) CGRect labelFrame; 48 @end 49 50 static void invokeUIViewControllerSelector(id instance, SEL selector) 51 { 52 objc_super superClass { instance, PAL::getUIViewControllerClass() }; 53 auto superclassFunction = reinterpret_cast<void(*)(objc_super*, SEL)>(objc_msgSendSuper); 54 superclassFunction(&superClass, selector); 55 } 56 57 static void WebValidationBubbleViewController_dealloc(WebValidationBubbleViewController *instance, SEL) 58 { 59 [instance.label release]; 60 [instance setValue:nil forKey:@"_label"]; 61 62 invokeUIViewControllerSelector(instance, @selector(dealloc)); 63 } 64 65 static void WebValidationBubbleViewController_viewDidLoad(WebValidationBubbleViewController *instance, SEL) 66 { 67 invokeUIViewControllerSelector(instance, @selector(viewDidLoad)); 68 69 UILabel *label = [PAL::allocUILabelInstance() init]; 70 label.font = [PAL::getUIFontClass() preferredFontForTextStyle:PAL::get_UIKit_UIFontTextStyleCallout()]; 71 label.lineBreakMode = NSLineBreakByTruncatingTail; 72 label.numberOfLines = 4; 73 [instance.view addSubview:label]; 74 [instance setValue:label forKey:@"_label"]; 75 } 76 77 static void WebValidationBubbleViewController_viewWillLayoutSubviews(WebValidationBubbleViewController *instance, SEL) 78 { 79 invokeUIViewControllerSelector(instance, @selector(viewWillLayoutSubviews)); 80 81 instance.label.frame = instance.labelFrame; 82 } 83 84 static void WebValidationBubbleViewController_viewSafeAreaInsetsDidChange(WebValidationBubbleViewController *instance, SEL) 85 { 86 invokeUIViewControllerSelector(instance, @selector(viewSafeAreaInsetsDidChange)); 87 88 instance.label.frame = instance.labelFrame; 89 } 90 91 static CGRect WebValidationBubbleViewController_labelFrame(WebValidationBubbleViewController *instance, SEL) 92 { 93 auto frameWithPadding = UIEdgeInsetsInsetRect(instance.view.bounds, instance.view.safeAreaInsets); 94 return UIEdgeInsetsInsetRect(frameWithPadding, UIEdgeInsetsMake(validationBubbleVerticalPadding, validationBubbleHorizontalPadding, validationBubbleVerticalPadding, validationBubbleHorizontalPadding)); 95 } 96 97 static UILabel *WebValidationBubbleViewController_label(WebValidationBubbleViewController *instance, SEL) 98 { 99 return [instance valueForKey:@"_label"]; 100 } 101 102 static WebValidationBubbleViewController *allocWebValidationBubbleViewControllerInstance() 103 { 104 static Class theClass = nil; 105 static dispatch_once_t onceToken; 106 dispatch_once(&onceToken, ^{ 107 theClass = objc_allocateClassPair(PAL::getUIViewControllerClass(), "WebValidationBubbleViewController", 0); 108 class_addMethod(theClass, @selector(dealloc), (IMP)WebValidationBubbleViewController_dealloc, "v@:"); 109 class_addMethod(theClass, @selector(viewDidLoad), (IMP)WebValidationBubbleViewController_viewDidLoad, "v@:"); 110 class_addMethod(theClass, @selector(viewWillLayoutSubviews), (IMP)WebValidationBubbleViewController_viewWillLayoutSubviews, "v@:"); 111 class_addMethod(theClass, @selector(viewSafeAreaInsetsDidChange), (IMP)WebValidationBubbleViewController_viewSafeAreaInsetsDidChange, "v@:"); 112 class_addMethod(theClass, @selector(label), (IMP)WebValidationBubbleViewController_label, "v@:"); 113 class_addMethod(theClass, @selector(labelFrame), (IMP)WebValidationBubbleViewController_labelFrame, "v@:"); 114 class_addIvar(theClass, "_label", sizeof(UILabel *), log2(sizeof(UILabel *)), "@"); 115 objc_registerClassPair(theClass); 116 }); 117 return (WebValidationBubbleViewController *)[theClass alloc]; 118 } 119 38 120 @interface WebValidationBubbleTapRecognizer : NSObject 39 121 @end … … 88 170 namespace WebCore { 89 171 90 static const CGFloat horizontalPadding = 17; 91 static const CGFloat verticalPadding = 9; 92 static const CGFloat maxLabelWidth = 300; 93 94 ValidationBubble::ValidationBubble(UIView* view, const String& message, const Settings&) 172 ValidationBubble::ValidationBubble(UIView *view, const String& message, const Settings&) 95 173 : m_view(view) 96 174 , m_message(message) 97 175 { 98 m_popoverController = adoptNS([ PAL::allocUIViewControllerInstance() init]);176 m_popoverController = adoptNS([allocWebValidationBubbleViewControllerInstance() init]); 99 177 [m_popoverController setModalPresentationStyle:UIModalPresentationPopover]; 100 101 RetainPtr<UIView> popoverView = adoptNS([PAL::allocUIViewInstance() initWithFrame:CGRectZero]);102 [m_popoverController setView:popoverView.get()];103 178 m_tapRecognizer = adoptNS([[WebValidationBubbleTapRecognizer alloc] initWithPopoverController:m_popoverController.get()]); 104 179 105 RetainPtr<UILabel> label = adoptNS([PAL::allocUILabelInstance() initWithFrame:CGRectZero]); 106 [label setText:message]; 107 [label setFont:[PAL::getUIFontClass() preferredFontForTextStyle:PAL::get_UIKit_UIFontTextStyleCallout()]]; 108 m_fontSize = [[label font] pointSize]; 109 [label setLineBreakMode:NSLineBreakByTruncatingTail]; 110 [label setNumberOfLines:4]; 111 [popoverView addSubview:label.get()]; 112 113 CGSize labelSize = [label sizeThatFits:CGSizeMake(maxLabelWidth, CGFLOAT_MAX)]; 114 [label setFrame:CGRectMake(horizontalPadding, verticalPadding, labelSize.width, labelSize.height)]; 115 [popoverView setFrame:CGRectMake(horizontalPadding, verticalPadding, labelSize.width + horizontalPadding * 2, labelSize.height + verticalPadding * 2)]; 116 117 [m_popoverController setPreferredContentSize:popoverView.get().frame.size]; 180 UILabel *label = [m_popoverController label]; 181 label.text = message; 182 m_fontSize = label.font.pointSize; 183 CGSize labelSize = [label sizeThatFits:CGSizeMake(validationBubbleMaxLabelWidth, CGFLOAT_MAX)]; 184 [m_popoverController setPreferredContentSize:CGSizeMake(labelSize.width + validationBubbleHorizontalPadding * 2, labelSize.height + validationBubbleVerticalPadding * 2)]; 118 185 } 119 186 … … 133 200 [m_presentingViewController presentViewController:m_popoverController.get() animated:NO completion:[protectedThis]() { 134 201 // Hide this popover from VoiceOver and instead announce the message. 135 [protectedThis->m_popoverController .get().view setAccessibilityElementsHidden:YES];202 [protectedThis->m_popoverController view].accessibilityElementsHidden = YES; 136 203 }]; 137 204 … … 149 216 } 150 217 151 void ValidationBubble::setAnchorRect(const IntRect& anchorRect, UIViewController *presentingViewController)218 void ValidationBubble::setAnchorRect(const IntRect& anchorRect, UIViewController *presentingViewController) 152 219 { 153 220 if (!presentingViewController) … … 157 224 m_popoverDelegate = adoptNS([[WebValidationBubbleDelegate alloc] init]); 158 225 presentationController.delegate = m_popoverDelegate.get(); 159 presentationController.passthroughViews = [NSArray arrayWithObjects:presentingViewController.view, m_view, nil]; 160 161 presentationController.permittedArrowDirections = UIPopoverArrowDirectionUp; 226 presentationController.passthroughViews = @[ presentingViewController.view, m_view ]; 162 227 presentationController.sourceView = m_view; 163 228 presentationController.sourceRect = CGRectMake(anchorRect.x(), anchorRect.y(), anchorRect.width(), anchorRect.height());
Note: See TracChangeset
for help on using the changeset viewer.