Changeset 276325 in webkit


Ignore:
Timestamp:
Apr 20, 2021, 3:45:13 PM (4 years ago)
Author:
Aditya Keerthi
Message:

[iOS][FCR] Update date/time picker appearance
https://bugs.webkit.org/show_bug.cgi?id=224794
<rdar://problem/76785859>

Reviewed by Wenson Hsieh.

Source/WebCore:

  • en.lproj/Localizable.strings:

Remove now unused string.

  • platform/LocalizedStrings.cpp:
  • platform/LocalizedStrings.h:
  • platform/cocoa/LocalizedStringsCocoa.mm:

(WebCore::formControlDoneButtonTitle):

Moved definition out of PLATFORM(WATCHOS) in LocalizedStrings.cpp and
into LocalizedStringsCocoa, so that the "Done" string can be used by
PLATFORM(IOS_FAMILY).

Source/WebKit:

Date/time pickers should have a system material background and should
avoid obscuring the associated element when possible.

  • Platform/spi/ios/UIKitSPI.h:

Add new SPI declarations to support date/time picker modifications.

  • UIProcess/ios/WKContentViewInteraction.mm:

(createTargetedPreview):
(createFallbackTargetedPreview):
(-[WKContentView _createTargetedContextMenuHintPreviewForFocusedElement]):

Set the UITargetedPreview background color to clearColor when
presenting a date/time picker, so that the presented picker has a
visible material effect. Without this change, the picker would have a
solid white or black background.

  • UIProcess/ios/forms/WKDateTimeInputControl.mm:

(-[WKDateTimePickerViewController initWithDelegate:]):
(-[WKDateTimePickerViewController viewDidLoad]):

Add a system material background to the date picker using
UIVisualEffectView.

(-[WKDateTimePickerViewController datePickerChanged:]):
(-[WKDateTimePickerViewController resetButtonPressed:]):
(-[WKDateTimePickerViewController doneButtonPressed:]):
(-[WKDateTimePickerViewController datePickerInsets]):
(-[WKDateTimePickerViewController preferredDatePickerSize]):
(-[WKDateTimePickerViewController preferredContentSize]):
(-[WKDateTimePickerViewController date]):
(-[WKDateTimePickerViewController setDate:]):
(-[WKDateTimePickerViewController setDatePickerMode:]):
(-[WKDateTimePickerViewController timeZone]):
(-[WKDateTimePickerViewController setTimeZone:]):
(-[WKDateTimePickerViewController calendar]):
(-[WKDateTimePicker initWithView:datePickerMode:]):
(-[WKDateTimePicker _preferredEdgeInsetsForDateTimePicker]):

Attempt to present the date picker in a way that does not obscure the
element.

(-[WKDateTimePicker _contextMenuInteraction:styleForMenuWithConfiguration:]):
(-[WKDateTimePicker contextMenuInteraction:configurationForMenuAtLocation:]):
(-[WKDateTimePicker dateTimePickerViewControllerDidChangeDate:]):
(-[WKDateTimePicker dateTimePickerViewControllerDidPressResetButton:]):
(-[WKDateTimePicker dateTimePickerViewControllerDidPressDoneButton:]):
(-[WKDateTimePicker shouldForceGregorianCalendar]):
(-[WKDateTimePicker dealloc]):
(-[WKDateTimePicker _timeZoneOffsetFromGMT:]):
(-[WKDateTimePicker _sanitizeInputValueForFormatter:]):
(-[WKDateTimePicker dateFormatterForPicker]):
(-[WKDateTimePicker _dateChangedSetAsNumber]):
(-[WKDateTimePicker _dateChangedSetAsString]):
(-[WKDateTimePicker setDateTimePickerToInitialValue]):
(-[WKDateTimePicker controlView]):

Updated this method to return nil, matching other form controls that
do not present a keyboard input view (example: <select>).

(-[WKDateTimePicker controlBeginEditing]):
(-[WKDateTimePicker controlEndEditing]):
(-[WKDateTimePicker calendarType]):
(-[WKDateTimePicker hour]):
(-[WKDateTimePicker minute]):
(-[WKDateTimePicker setHour:minute:]):

Location:
trunk/Source
Files:
9 edited

Legend:

Unmodified
Added
Removed
  • trunk/Source/WebCore/ChangeLog

    r276317 r276325  
     12021-04-20  Aditya Keerthi  <akeerthi@apple.com>
     2
     3        [iOS][FCR] Update date/time picker appearance
     4        https://bugs.webkit.org/show_bug.cgi?id=224794
     5        <rdar://problem/76785859>
     6
     7        Reviewed by Wenson Hsieh.
     8
     9        * en.lproj/Localizable.strings:
     10
     11        Remove now unused string.
     12
     13        * platform/LocalizedStrings.cpp:
     14        * platform/LocalizedStrings.h:
     15        * platform/cocoa/LocalizedStringsCocoa.mm:
     16        (WebCore::formControlDoneButtonTitle):
     17
     18        Moved definition out of PLATFORM(WATCHOS) in LocalizedStrings.cpp and
     19        into LocalizedStringsCocoa, so that the "Done" string can be used by
     20        PLATFORM(IOS_FAMILY).
     21
    1222021-04-20  Fujii Hironori  <Hironori.Fujii@sony.com>
    223
  • trunk/Source/WebCore/en.lproj/Localizable.strings

    r275358 r276325  
    332332"Donate with Apple Pay" = "Donate with Apple Pay";
    333333
    334 /* Title of the Done button for zoomed form controls. */
     334/* Title of the Done button for form controls. */
    335335"Done" = "Done";
    336336
     
    587587"Numbered list" = "Numbered list";
    588588
    589 /* Title of the OK button in date/time form controls. */
    590 "OK (OK button title in date/time picker)" = "OK";
    591 
    592589/* Title of the OK button for the number pad in zoomed form controls. */
    593590"OK (OK button title in extra zoomed number pad)" = "OK";
  • trunk/Source/WebCore/platform/LocalizedStrings.cpp

    r274148 r276325  
    11901190}
    11911191
    1192 String formControlDoneButtonTitle()
    1193 {
    1194     return WEB_UI_STRING("Done", "Title of the Done button for zoomed form controls.");
    1195 }
    1196 
    11971192String formControlCancelButtonTitle()
    11981193{
  • trunk/Source/WebCore/platform/LocalizedStrings.h

    r274698 r276325  
    266266    String fileButtonNoMediaFileSelectedLabel();
    267267    String fileButtonNoMediaFilesSelectedLabel();
     268
     269    WEBCORE_EXPORT String formControlDoneButtonTitle();
    268270#endif
    269271
     
    335337    WEBCORE_EXPORT String numberPadOKButtonTitle();
    336338    WEBCORE_EXPORT String formControlCancelButtonTitle();
    337     WEBCORE_EXPORT String formControlDoneButtonTitle();
    338339    WEBCORE_EXPORT String formControlHideButtonTitle();
    339340    WEBCORE_EXPORT String formControlGoButtonTitle();
  • trunk/Source/WebCore/platform/cocoa/LocalizedStringsCocoa.mm

    r272997 r276325  
    268268    return WEB_UI_STRING("no media selected (multiple)", "Text to display in file button used in HTML forms for media files when no media files are selected and the button allows multiple files to be selected");
    269269}
     270
     271
     272String formControlDoneButtonTitle()
     273{
     274    return WEB_UI_STRING("Done", "Title of the Done button for form controls.");
     275}
    270276#endif
    271277
  • trunk/Source/WebKit/ChangeLog

    r276324 r276325  
     12021-04-20  Aditya Keerthi  <akeerthi@apple.com>
     2
     3        [iOS][FCR] Update date/time picker appearance
     4        https://bugs.webkit.org/show_bug.cgi?id=224794
     5        <rdar://problem/76785859>
     6
     7        Reviewed by Wenson Hsieh.
     8
     9        Date/time pickers should have a system material background and should
     10        avoid obscuring the associated element when possible.
     11
     12        * Platform/spi/ios/UIKitSPI.h:
     13
     14        Add new SPI declarations to support date/time picker modifications.
     15
     16        * UIProcess/ios/WKContentViewInteraction.mm:
     17        (createTargetedPreview):
     18        (createFallbackTargetedPreview):
     19        (-[WKContentView _createTargetedContextMenuHintPreviewForFocusedElement]):
     20
     21        Set the UITargetedPreview background color to clearColor when
     22        presenting a date/time picker, so that the presented picker has a
     23        visible material effect. Without this change, the picker would have a
     24        solid white or black background.
     25
     26        * UIProcess/ios/forms/WKDateTimeInputControl.mm:
     27        (-[WKDateTimePickerViewController initWithDelegate:]):
     28        (-[WKDateTimePickerViewController viewDidLoad]):
     29
     30        Add a system material background to the date picker using
     31        UIVisualEffectView.
     32
     33        (-[WKDateTimePickerViewController datePickerChanged:]):
     34        (-[WKDateTimePickerViewController resetButtonPressed:]):
     35        (-[WKDateTimePickerViewController doneButtonPressed:]):
     36        (-[WKDateTimePickerViewController datePickerInsets]):
     37        (-[WKDateTimePickerViewController preferredDatePickerSize]):
     38        (-[WKDateTimePickerViewController preferredContentSize]):
     39        (-[WKDateTimePickerViewController date]):
     40        (-[WKDateTimePickerViewController setDate:]):
     41        (-[WKDateTimePickerViewController setDatePickerMode:]):
     42        (-[WKDateTimePickerViewController timeZone]):
     43        (-[WKDateTimePickerViewController setTimeZone:]):
     44        (-[WKDateTimePickerViewController calendar]):
     45        (-[WKDateTimePicker initWithView:datePickerMode:]):
     46        (-[WKDateTimePicker _preferredEdgeInsetsForDateTimePicker]):
     47
     48        Attempt to present the date picker in a way that does not obscure the
     49        element.
     50
     51        (-[WKDateTimePicker _contextMenuInteraction:styleForMenuWithConfiguration:]):
     52        (-[WKDateTimePicker contextMenuInteraction:configurationForMenuAtLocation:]):
     53        (-[WKDateTimePicker dateTimePickerViewControllerDidChangeDate:]):
     54        (-[WKDateTimePicker dateTimePickerViewControllerDidPressResetButton:]):
     55        (-[WKDateTimePicker dateTimePickerViewControllerDidPressDoneButton:]):
     56        (-[WKDateTimePicker shouldForceGregorianCalendar]):
     57        (-[WKDateTimePicker dealloc]):
     58        (-[WKDateTimePicker _timeZoneOffsetFromGMT:]):
     59        (-[WKDateTimePicker _sanitizeInputValueForFormatter:]):
     60        (-[WKDateTimePicker dateFormatterForPicker]):
     61        (-[WKDateTimePicker _dateChangedSetAsNumber]):
     62        (-[WKDateTimePicker _dateChangedSetAsString]):
     63        (-[WKDateTimePicker setDateTimePickerToInitialValue]):
     64        (-[WKDateTimePicker controlView]):
     65
     66        Updated this method to return nil, matching other form controls that
     67        do not present a keyboard input view (example: <select>).
     68
     69        (-[WKDateTimePicker controlBeginEditing]):
     70        (-[WKDateTimePicker controlEndEditing]):
     71        (-[WKDateTimePicker calendarType]):
     72        (-[WKDateTimePicker hour]):
     73        (-[WKDateTimePicker minute]):
     74        (-[WKDateTimePicker setHour:minute:]):
     75
    1762021-04-20  Keith Miller  <keith_miller@apple.com>
    277
  • trunk/Source/WebKit/Platform/spi/ios/UIKitSPI.h

    r276312 r276325  
    216216@property (nonatomic, readwrite, assign) UIDatePickerStyle preferredDatePickerStyle;
    217217#endif
     218- (UIEdgeInsets)_appliedInsetsToEdgeOfContent;
    218219@end
    219220
     
    11981199typedef NS_ENUM(NSUInteger, _UIContextMenuLayout) {
    11991200    _UIContextMenuLayoutActionsOnly = 1,
     1201    _UIContextMenuLayoutPreviewOnly = 2,
    12001202    _UIContextMenuLayoutCompactMenu = 3,
    12011203    _UIContextMenuLayoutAutomatic = 100,
     
    12061208@property (nonatomic) UIEdgeInsets preferredEdgeInsets;
    12071209@property (nonatomic) BOOL hasInteractivePreview;
     1210@property (nonatomic) BOOL prefersCenteredPreviewWhenActionsAreAbsent;
     1211@property (nonatomic) BOOL ignoresDefaultSizingRules;
    12081212@property (nonatomic, strong) NSArray *preferredBackgroundEffects;
    12091213+ (instancetype)defaultStyle;
     
    14881492
    14891493UIEdgeInsets UIEdgeInsetsAdd(UIEdgeInsets lhs, UIEdgeInsets rhs, UIRectEdge);
     1494UIEdgeInsets UIEdgeInsetsSubtract(UIEdgeInsets lhs, UIEdgeInsets rhs, UIRectEdge);
    14901495
    14911496extern NSString *const UIBacklightLevelChangedNotification;
  • trunk/Source/WebKit/UIProcess/ios/WKContentViewInteraction.mm

    r276219 r276325  
    83558355}
    83568356
    8357 static RetainPtr<UITargetedPreview> createFallbackTargetedPreview(UIView *rootView, UIView *containerView, const WebCore::FloatRect& frameInRootViewCoordinates)
     8357static RetainPtr<UITargetedPreview> createFallbackTargetedPreview(UIView *rootView, UIView *containerView, const WebCore::FloatRect& frameInRootViewCoordinates, UIColor *backgroundColor)
    83588358{
    83598359    if (!containerView.window)
     
    83648364
    83658365    auto parameters = adoptNS([[UIPreviewParameters alloc] init]);
     8366    if (backgroundColor)
     8367        [parameters setBackgroundColor:backgroundColor];
     8368
    83668369    UIView *snapshotView = [rootView resizableSnapshotViewFromRect:frameInRootViewCoordinates afterScreenUpdates:NO withCapInsets:UIEdgeInsetsZero];
    83678370
     
    83818384- (UITargetedPreview *)_createTargetedContextMenuHintPreviewForFocusedElement
    83828385{
    8383     RetainPtr<UITargetedPreview> targetedPreview = createFallbackTargetedPreview(self, self.containerForContextMenuHintPreviews, _focusedElementInformation.interactionRect);
     8386    auto backgroundColor = [&]() -> UIColor * {
     8387        switch (_focusedElementInformation.elementType) {
     8388        case WebKit::InputType::Date:
     8389        case WebKit::InputType::Month:
     8390        case WebKit::InputType::DateTimeLocal:
     8391        case WebKit::InputType::Time:
     8392            return UIColor.clearColor;
     8393        default:
     8394            return nil;
     8395        }
     8396    }();
     8397
     8398    auto targetedPreview = createFallbackTargetedPreview(self, self.containerForContextMenuHintPreviews, _focusedElementInformation.interactionRect, backgroundColor);
    83848399
    83858400    [self _updateTargetedPreviewScrollViewUsingContainerScrollingNodeID:_focusedElementInformation.containerScrollingNodeID];
     
    84048419
    84058420    if (!targetedPreview)
    8406         targetedPreview = createFallbackTargetedPreview(self, self.containerForContextMenuHintPreviews, _positionInformation.bounds);
     8421        targetedPreview = createFallbackTargetedPreview(self, self.containerForContextMenuHintPreviews, _positionInformation.bounds, nil);
    84078422
    84088423    [self _updateTargetedPreviewScrollViewUsingContainerScrollingNodeID:_positionInformation.containerScrollingNodeID];
  • trunk/Source/WebKit/UIProcess/ios/forms/WKDateTimeInputControl.mm

    r275532 r276325  
    3434#import "WKWebViewPrivateForTesting.h"
    3535#import "WebPageProxy.h"
    36 #import <UIKit/UIBarButtonItem.h>
    3736#import <UIKit/UIDatePicker.h>
    3837#import <WebCore/LocalizedStrings.h>
     
    4039#import <wtf/RetainPtr.h>
    4140
    42 @interface WKDateTimeContextMenuViewController : UIViewController
     41@class WKDateTimePickerViewController;
     42
     43@protocol WKDateTimePickerViewControllerDelegate <NSObject>
     44- (void)dateTimePickerViewControllerDidChangeDate:(WKDateTimePickerViewController *)dateTimePickerViewController;
     45- (void)dateTimePickerViewControllerDidPressResetButton:(WKDateTimePickerViewController *)dateTimePickerViewController;
     46- (void)dateTimePickerViewControllerDidPressDoneButton:(WKDateTimePickerViewController *)dateTimePickerViewController;
     47
     48- (BOOL)shouldForceGregorianCalendar;
     49@end
     50
     51@interface WKDateTimePickerViewController : UIViewController
     52- (instancetype)initWithDelegate:(id<WKDateTimePickerViewControllerDelegate>)delegate;
     53@end
     54
     55@implementation WKDateTimePickerViewController {
     56    CGSize _contentSize;
     57
     58    RetainPtr<UIDatePicker> _datePicker;
     59    WeakObjCPtr<id <WKDateTimePickerViewControllerDelegate>> _delegate;
     60}
     61
     62static const CGFloat kDateTimePickerButtonFontSize = 17;
     63static const CGFloat kDateTimePickerToolbarHeight = 44;
     64static const CGFloat kDateTimePickerSeparatorHeight = 1;
     65static const CGFloat kDateTimePickerViewMargin = 16;
     66
     67static const CGFloat kDateTimePickerDefaultWidth = 320;
     68static const CGFloat kDateTimePickerTimeControlWidth = 218;
     69static const CGFloat kDateTimePickerTimeControlHeight = 172;
     70
     71- (instancetype)initWithDelegate:(id <WKDateTimePickerViewControllerDelegate>)delegate
     72{
     73    if (!(self = [super init]))
     74        return nil;
     75
     76    _delegate = delegate;
     77
     78    _datePicker = adoptNS([[UIDatePicker alloc] init]);
     79    [_datePicker addTarget:self action:@selector(datePickerChanged:) forControlEvents:UIControlEventValueChanged];
     80    [_datePicker setTranslatesAutoresizingMaskIntoConstraints:NO];
     81
     82    if ([_delegate shouldForceGregorianCalendar])
     83        [_datePicker setCalendar:[NSCalendar calendarWithIdentifier:NSCalendarIdentifierGregorian]];
     84
     85    return self;
     86}
     87
     88- (void)viewDidLoad
     89{
     90    [super viewDidLoad];
     91
     92    CGSize contentSize = self.preferredContentSize;
     93
     94    auto backgroundView = adoptNS([[UIVisualEffectView alloc] initWithEffect:[UIBlurEffect effectWithStyle:UIBlurEffectStyleSystemMaterial]]);
     95    [backgroundView setFrame:CGRectMake(0, 0, contentSize.width, contentSize.height)];
     96    [self.view addSubview:backgroundView.get()];
     97
     98    [[backgroundView contentView] addSubview:_datePicker.get()];
     99
     100    CGSize datePickerSize = self.preferredDatePickerSize;
     101    UIEdgeInsets datePickerInsets = [self datePickerInsets];
     102
     103    [NSLayoutConstraint activateConstraints:@[
     104        [[_datePicker topAnchor] constraintEqualToAnchor:[backgroundView contentView].topAnchor constant:datePickerInsets.top],
     105        [[_datePicker leadingAnchor] constraintEqualToAnchor:[backgroundView contentView].leadingAnchor constant:datePickerInsets.left],
     106        [[_datePicker widthAnchor] constraintEqualToConstant:datePickerSize.width],
     107        [[_datePicker heightAnchor] constraintEqualToConstant:datePickerSize.height],
     108    ]];
     109
     110    auto toolbarView = adoptNS([[UIView alloc] init]);
     111    [toolbarView setTranslatesAutoresizingMaskIntoConstraints:NO];
     112    [[backgroundView contentView] addSubview:toolbarView.get()];
     113
     114    [NSLayoutConstraint activateConstraints:@[
     115        [[toolbarView bottomAnchor] constraintEqualToAnchor:[backgroundView contentView].bottomAnchor],
     116        [[toolbarView leadingAnchor] constraintEqualToAnchor:[backgroundView contentView].leadingAnchor],
     117        [[toolbarView heightAnchor] constraintEqualToConstant:kDateTimePickerToolbarHeight],
     118        [[toolbarView widthAnchor] constraintEqualToAnchor:[backgroundView contentView].widthAnchor],
     119    ]];
     120
     121    auto separatorView = adoptNS([[UIView alloc] init]);
     122    [separatorView setBackgroundColor:UIColor.separatorColor];
     123
     124    NSString *resetString = WEB_UI_STRING_KEY("Reset", "Reset Button Date/Time Context Menu", "Reset button in date input context menu");
     125    UIButton *resetButton = [UIButton buttonWithType:UIButtonTypeSystem];
     126    resetButton.titleLabel.font = [UIFont systemFontOfSize:kDateTimePickerButtonFontSize];
     127    [resetButton setTitle:resetString forState:UIControlStateNormal];
     128    [resetButton addTarget:self action:@selector(resetButtonPressed:) forControlEvents:UIControlEventTouchUpInside];
     129
     130    NSString *doneString = WebCore::formControlDoneButtonTitle();
     131    UIButton *doneButton = [UIButton buttonWithType:UIButtonTypeSystem];
     132    doneButton.titleLabel.font = [UIFont boldSystemFontOfSize:kDateTimePickerButtonFontSize];
     133    [doneButton setTitle:doneString forState:UIControlStateNormal];
     134    [doneButton addTarget:self action:@selector(doneButtonPressed:) forControlEvents:UIControlEventTouchUpInside];
     135
     136    for (UIView *subview in @[separatorView.get(), resetButton, doneButton]) {
     137        subview.translatesAutoresizingMaskIntoConstraints = NO;
     138        [toolbarView addSubview:subview];
     139    }
     140
     141    [NSLayoutConstraint activateConstraints:@[
     142        [[separatorView topAnchor] constraintEqualToAnchor:[toolbarView topAnchor]],
     143        [[separatorView leadingAnchor] constraintEqualToAnchor:[toolbarView leadingAnchor]],
     144        [[separatorView heightAnchor] constraintEqualToConstant:kDateTimePickerSeparatorHeight],
     145        [[separatorView widthAnchor] constraintEqualToAnchor:[toolbarView widthAnchor]],
     146    ]];
     147
     148    [NSLayoutConstraint activateConstraints:@[
     149        [resetButton.leadingAnchor constraintEqualToAnchor:[toolbarView leadingAnchor] constant:kDateTimePickerViewMargin],
     150        [resetButton.topAnchor constraintEqualToAnchor:[toolbarView topAnchor]],
     151        [resetButton.bottomAnchor constraintEqualToAnchor:[toolbarView bottomAnchor]],
     152    ]];
     153
     154    [NSLayoutConstraint activateConstraints:@[
     155        [doneButton.trailingAnchor constraintEqualToAnchor:[toolbarView trailingAnchor] constant:-kDateTimePickerViewMargin],
     156        [doneButton.topAnchor constraintEqualToAnchor:[toolbarView topAnchor]],
     157        [doneButton.bottomAnchor constraintEqualToAnchor:[toolbarView bottomAnchor]],
     158    ]];
     159}
     160
     161- (void)datePickerChanged:(id)sender
     162{
     163    [_delegate dateTimePickerViewControllerDidChangeDate:self];
     164}
     165
     166- (void)resetButtonPressed:(id)sender
     167{
     168    [_delegate dateTimePickerViewControllerDidPressResetButton:self];
     169}
     170
     171- (void)doneButtonPressed:(id)sender
     172{
     173    [_delegate dateTimePickerViewControllerDidPressDoneButton:self];
     174}
     175
     176- (UIEdgeInsets)datePickerInsets
     177{
     178    UIEdgeInsets expectedInsets = UIEdgeInsetsMake(kDateTimePickerViewMargin, kDateTimePickerViewMargin, kDateTimePickerViewMargin, kDateTimePickerViewMargin);
     179    UIEdgeInsets appliedInsets = [_datePicker _appliedInsetsToEdgeOfContent];
     180    return UIEdgeInsetsSubtract(expectedInsets, appliedInsets, UIRectEdgeAll);
     181}
     182
     183- (CGSize)preferredDatePickerSize
     184{
     185    CGSize fittingSize = UILayoutFittingCompressedSize;
     186    UILayoutPriority horizontalPriority = UILayoutPriorityFittingSizeLevel;
     187    UILayoutPriority verticalPriority = UILayoutPriorityFittingSizeLevel;
     188    if ([_datePicker datePickerMode] != UIDatePickerModeTime)
     189        fittingSize.width = kDateTimePickerDefaultWidth;
     190    else {
     191        fittingSize.width = kDateTimePickerTimeControlWidth;
     192        fittingSize.height = kDateTimePickerTimeControlHeight;
     193        horizontalPriority = UILayoutPriorityRequired;
     194        verticalPriority = UILayoutPriorityRequired;
     195    }
     196
     197    CGSize layoutSize = [_datePicker systemLayoutSizeFittingSize:fittingSize withHorizontalFittingPriority:horizontalPriority verticalFittingPriority:verticalPriority];
     198    return layoutSize;
     199}
     200
     201- (CGSize)preferredContentSize
     202{
     203    // Cache the content size to workaround rdar://74749942.
     204    if (CGSizeEqualToSize(_contentSize, CGSizeZero)) {
     205        CGSize datePickerSize = [self preferredDatePickerSize];
     206        UIEdgeInsets datePickerInsets = [self datePickerInsets];
     207
     208        _contentSize = CGSizeMake(datePickerSize.width + datePickerInsets.left + datePickerInsets.right, datePickerSize.height + kDateTimePickerToolbarHeight + datePickerInsets.top + datePickerInsets.bottom);
     209    }
     210
     211    return _contentSize;
     212}
     213
     214- (NSDate *)date
     215{
     216    return [_datePicker date];
     217}
     218
     219- (void)setDate:(NSDate *)date
     220{
     221    [_datePicker setDate:date];
     222}
     223
     224- (void)setDatePickerMode:(UIDatePickerMode)mode
     225{
     226    [_datePicker setDatePickerMode:mode];
     227
     228#if HAVE(UIDATEPICKER_STYLE)
     229    if (mode == UIDatePickerModeTime || mode == (UIDatePickerMode)UIDatePickerModeYearAndMonth)
     230        [_datePicker setPreferredDatePickerStyle:UIDatePickerStyleWheels];
     231    else
     232        [_datePicker setPreferredDatePickerStyle:UIDatePickerStyleInline];
     233#endif
     234}
     235
     236- (NSTimeZone *)timeZone
     237{
     238    return [_datePicker timeZone];
     239}
     240
     241- (void)setTimeZone:(NSTimeZone *)timeZone
     242{
     243    return [_datePicker setTimeZone:timeZone];
     244}
     245
     246- (NSCalendar *)calendar
     247{
     248    return [_datePicker calendar];
     249}
     250
    43251@end
    44252
     
    47255, UIContextMenuInteractionDelegate
    48256#endif
     257, WKDateTimePickerViewControllerDelegate
    49258> {
    50     RetainPtr<UIDatePicker> _datePicker;
    51259    NSString *_formatString;
    52260    RetainPtr<NSString> _initialValue;
    53261    NSTimeInterval _initialValueAsNumber;
    54262    BOOL _shouldRemoveTimeZoneInformation;
    55     BOOL _isTimeInput;
    56263    WKContentView *_view;
    57264    CGPoint _interactionPoint;
    58     RetainPtr<WKDateTimeContextMenuViewController> _viewController;
    59265#if USE(UICONTEXTMENU)
    60266    RetainPtr<UIContextMenuInteraction> _dateTimeContextMenuInteraction;
    61267#endif
    62     BOOL _presenting;
    63     BOOL _preservingFocus;
    64 }
     268    RetainPtr<WKDateTimePickerViewController> _dateTimePickerViewController;
     269}
     270
    65271- (instancetype)initWithView:(WKContentView *)view datePickerMode:(UIDatePickerMode)mode;
    66 - (WKDateTimeContextMenuViewController *)viewController;
     272
    67273@property (nonatomic, readonly) NSString *calendarType;
    68274@property (nonatomic, readonly) double hour;
    69275@property (nonatomic, readonly) double minute;
    70276- (void)setHour:(NSInteger)hour minute:(NSInteger)minute;
    71 @end
    72 
    73 @implementation WKDateTimeContextMenuViewController
    74 
    75 - (CGSize)preferredContentSize
    76 {
    77     // FIXME: Workaround, should be able to be readdressed after <rdar://problem/64143534>
    78     UIView *view = self.view.subviews[0];
    79     if (UIEdgeInsetsEqualToEdgeInsets(view.layoutMargins, UIEdgeInsetsZero)) {
    80         view.translatesAutoresizingMaskIntoConstraints = NO;
    81         [view layoutIfNeeded];
    82         view.translatesAutoresizingMaskIntoConstraints = YES;
    83         view.layoutMargins = UIEdgeInsetsMake(16, 16, 16, 16);
    84     }
    85     auto size = [view systemLayoutSizeFittingSize:UILayoutFittingCompressedSize];
    86    
    87     size.width = std::max<CGFloat>(size.width, 250.0);
    88     return size;
    89 }
    90277
    91278@end
     
    98285static const NSTimeInterval kMillisecondsPerSecond = 1000;
    99286
    100 #if HAVE(UIDATEPICKER_STYLE)
    101 - (UIDatePickerStyle)datePickerStyle
    102 {
    103     switch (_view.focusedElementInformation.elementType) {
    104     case WebKit::InputType::Month:
    105     case WebKit::InputType::Time:
    106         return UIDatePickerStyleWheels;
    107     default:
    108         return UIDatePickerStyleInline;
    109     }
    110 }
    111 #endif
     287static const CGFloat kDateTimePickerControlMargin = 6;
    112288
    113289- (id)initWithView:(WKContentView *)view datePickerMode:(UIDatePickerMode)mode
     
    115291    if (!(self = [super init]))
    116292        return nil;
     293
    117294    _view = view;
    118295    _interactionPoint = [_view lastInteractionLocation];
    119296    _shouldRemoveTimeZoneInformation = NO;
    120     _isTimeInput = NO;
     297
    121298    switch (view.focusedElementInformation.elementType) {
    122299    case WebKit::InputType::Date:
     
    128305    case WebKit::InputType::Time:
    129306        _formatString = kTimeFormatString;
    130         _isTimeInput = YES;
    131307        break;
    132308    case WebKit::InputType::DateTimeLocal:
     
    136312        break;
    137313    }
    138    
    139     _datePicker = adoptNS([[UIDatePicker alloc] init]);
    140 
    141     [_datePicker setDatePickerMode:mode];
    142     [_datePicker setHidden:NO];
    143    
    144 #if HAVE(UIDATEPICKER_STYLE)
    145     [_datePicker setPreferredDatePickerStyle:[self datePickerStyle]];
    146 #endif
    147     if ([self shouldPresentGregorianCalendar:view.focusedElementInformation])
    148         _datePicker.get().calendar = [NSCalendar calendarWithIdentifier:NSCalendarIdentifierGregorian];
    149    
    150     [_datePicker addTarget:self action:@selector(_dateChangeHandler:) forControlEvents:UIControlEventValueChanged];
    151    
     314
     315    _dateTimePickerViewController = adoptNS([[WKDateTimePickerViewController alloc] initWithDelegate:self]);
     316    [_dateTimePickerViewController setDatePickerMode:mode];
     317
    152318    return self;
    153319}
    154320
    155321#if USE(UICONTEXTMENU)
     322
     323- (UIEdgeInsets)_preferredEdgeInsetsForDateTimePicker
     324{
     325    CGSize pickerSize = [_dateTimePickerViewController preferredContentSize];
     326    CGRect windowBounds = _view.textEffectsWindow.bounds;
     327    CGRect elementFrameInWindowCoordinates = [_view convertRect:_view.focusedElementInformation.interactionRect toView:nil];
     328
     329    // Attempt to present the date picker in a way that does not obscure the element.
     330
     331    CGFloat topInsetForBottomAlignment = CGRectGetMaxY(elementFrameInWindowCoordinates) + kDateTimePickerControlMargin;
     332    CGFloat rightInsetForRightAlignment = CGRectGetWidth(windowBounds) - CGRectGetMaxX(elementFrameInWindowCoordinates);
     333
     334    BOOL canPresentBelowElement = (topInsetForBottomAlignment + pickerSize.height) < CGRectGetHeight(windowBounds);
     335    BOOL canAlignToElementRight = (rightInsetForRightAlignment + pickerSize.width) < CGRectGetWidth(windowBounds);
     336
     337    // Try to present the picker from the bottom right of the element.
     338    if (canPresentBelowElement && canAlignToElementRight)
     339        return UIEdgeInsetsMake(topInsetForBottomAlignment, 0, 0, rightInsetForRightAlignment);
     340
     341    CGFloat leftInsetForLeftAlignment = CGRectGetMinX(elementFrameInWindowCoordinates);
     342
     343    BOOL canAlignToElementLeft = (leftInsetForLeftAlignment + pickerSize.width) <= CGRectGetWidth(windowBounds);
     344
     345    // Try to present the picker from the bottom left of the element.
     346    if (canPresentBelowElement && canAlignToElementLeft)
     347        return UIEdgeInsetsMake(topInsetForBottomAlignment, leftInsetForLeftAlignment, 0, 0);
     348
     349    // Try to present the picker underneath the element.
     350    if (canPresentBelowElement)
     351        return UIEdgeInsetsMake(topInsetForBottomAlignment, 0, 0, 0);
     352
     353    CGFloat bottomInsetForTopAlignment = CGRectGetHeight(windowBounds) - CGRectGetMinY(elementFrameInWindowCoordinates) + kDateTimePickerControlMargin;
     354
     355    BOOL canPresentAboveElement = (bottomInsetForTopAlignment + pickerSize.height) < CGRectGetHeight(windowBounds);
     356
     357    // Try to present the picker from the top right of the element.
     358    if (canPresentAboveElement && canAlignToElementRight)
     359        return UIEdgeInsetsMake(0, 0, bottomInsetForTopAlignment, rightInsetForRightAlignment);
     360
     361    // Try to present the picker from the top left of the element.
     362    if (canPresentAboveElement && canAlignToElementLeft)
     363        return UIEdgeInsetsMake(0, leftInsetForLeftAlignment, bottomInsetForTopAlignment, 0);
     364
     365    // Try to present the picker above the element.
     366    if (canPresentAboveElement)
     367        return UIEdgeInsetsMake(0, 0, bottomInsetForTopAlignment, 0);
     368
     369    CGFloat rightInsetForPresentingBesideElementLeft = CGRectGetWidth(windowBounds) - CGRectGetMinX(elementFrameInWindowCoordinates) + kDateTimePickerControlMargin;
     370    BOOL canPresentBesideElementLeft = (rightInsetForPresentingBesideElementLeft + pickerSize.width) < CGRectGetWidth(windowBounds);
     371
     372    // Try to present the picker to the left of the element.
     373    if (canPresentBesideElementLeft)
     374        return UIEdgeInsetsMake(0, 0, 0, rightInsetForPresentingBesideElementLeft);
     375
     376    CGFloat leftInsetForPresentingBesideElementRight = CGRectGetMaxX(elementFrameInWindowCoordinates) + kDateTimePickerControlMargin;
     377    BOOL canPresentBesideElementRight = (leftInsetForPresentingBesideElementRight + pickerSize.width) < CGRectGetWidth(windowBounds);
     378
     379    // Try to present the picker to the right of the element.
     380    if (canPresentBesideElementRight)
     381        return UIEdgeInsetsMake(0, leftInsetForPresentingBesideElementRight, 0, 0);
     382
     383    // Present the picker from the center of the element.
     384    return UIEdgeInsetsZero;
     385}
    156386
    157387- (UITargetedPreview *)contextMenuInteraction:(UIContextMenuInteraction *)interaction previewForHighlightingMenuWithConfiguration:(UIContextMenuConfiguration *)configuration
     
    165395    style.hasInteractivePreview = YES;
    166396    style.preferredBackgroundEffects = @[ [UIVisualEffect emptyEffect] ];
    167     style.preferredLayout = _UIContextMenuLayoutAutomatic;
     397    style.preferredLayout = _UIContextMenuLayoutPreviewOnly;
     398    style.prefersCenteredPreviewWhenActionsAreAbsent = NO;
     399    style.ignoresDefaultSizingRules = YES;
     400    style.preferredEdgeInsets = [self _preferredEdgeInsetsForDateTimePicker];
    168401    return style;
    169402}
     
    172405{
    173406    return [UIContextMenuConfiguration configurationWithIdentifier:@"_UIDatePickerCompactEditor" previewProvider:^{
    174         [_viewController setView:nil];
    175         _viewController = adoptNS([[WKDateTimeContextMenuViewController alloc] init]);
    176         RetainPtr<UINavigationController> navigationController = adoptNS([[UINavigationController alloc] initWithRootViewController:_viewController.get()]);
    177        
    178         NSString *resetString = WEB_UI_STRING_KEY("Reset", "Reset Button Date/Time Context Menu", "Reset button in date input context menu");
    179         NSString *okString = WEB_UI_STRING_KEY("OK", "OK (OK button title in date/time picker)", "Title of the OK button in date/time form controls.");
    180 
    181         RetainPtr<UIBarButtonItem> okBarButton = adoptNS([[UIBarButtonItem alloc] initWithTitle:okString style:UIBarButtonItemStyleDone target:self action:@selector(ok:)]);
    182         RetainPtr<UIBarButtonItem> blankBarButton = adoptNS([[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemFlexibleSpace target:nil action:nil]);
    183         RetainPtr<UIBarButtonItem> resetBarButton = adoptNS([[UIBarButtonItem alloc] initWithTitle:resetString style:UIBarButtonItemStylePlain target:self action:@selector(reset:)]);
    184        
    185         [_viewController setToolbarItems:@[resetBarButton.get(), blankBarButton.get(), okBarButton.get()]];
    186         [navigationController setToolbarHidden:NO];
    187        
    188         auto centeringView = adoptNS([[UIView alloc] init]);
    189        
    190         [centeringView addSubview:_datePicker.get()];
    191        
    192         _datePicker.get().translatesAutoresizingMaskIntoConstraints = NO;
    193         auto widthConstraint = [[centeringView widthAnchor] constraintGreaterThanOrEqualToAnchor:[_datePicker widthAnchor]];
    194         auto heightConstraint = [[centeringView heightAnchor] constraintEqualToAnchor:[_datePicker heightAnchor]];
    195         auto horizontalConstraint = [[centeringView centerXAnchor] constraintEqualToAnchor:[_datePicker centerXAnchor]];
    196         auto verticalConstraint = [[centeringView centerYAnchor] constraintEqualToAnchor:[_datePicker centerYAnchor]];
    197 
    198         [NSLayoutConstraint activateConstraints:@[verticalConstraint, horizontalConstraint, widthConstraint, heightConstraint]];
    199 
    200         [_viewController setView:centeringView.get()];
    201 
    202         NSString *titleText = _view.inputLabelText;
    203         if (![titleText isEqual:@""]) {
    204             RetainPtr<UILabel> title = adoptNS([[UILabel alloc] init]);
    205             [title setFont:[UIFont preferredFontForTextStyle:UIFontTextStyleFootnote]];
    206             [title setText:titleText];
    207             [title setTextColor:[UIColor secondaryLabelColor]];
    208             [title setTextAlignment:NSTextAlignmentNatural];
    209             RetainPtr<UIBarButtonItem> titleButton = adoptNS([[UIBarButtonItem alloc] initWithCustomView:title.get()]);
    210             [_viewController navigationItem].leftBarButtonItem = titleButton.get();
    211         } else
    212             [navigationController setNavigationBarHidden:YES animated:NO];
    213        
    214         [navigationController navigationBar].translucent = NO;
    215         [navigationController navigationBar].barTintColor = [UIColor systemBackgroundColor];
    216         [navigationController toolbar].translucent = NO;
    217         [navigationController toolbar].barTintColor = [UIColor systemBackgroundColor];
    218         return navigationController.get();
     407        return _dateTimePickerViewController.get();
    219408    } actionProvider:nil];
    220409}
     
    268457#endif
    269458
    270 - (void)reset:(id)sender
     459- (void)dateTimePickerViewControllerDidChangeDate:(WKDateTimePickerViewController *)dateTimePickerViewController
     460{
     461    [self _dateChanged];
     462}
     463
     464- (void)dateTimePickerViewControllerDidPressResetButton:(WKDateTimePickerViewController *)dateTimePickerViewController
    271465{
    272466    [self setDateTimePickerToInitialValue];
     
    274468}
    275469
    276 - (void)ok:(id)sender
     470- (void)dateTimePickerViewControllerDidPressDoneButton:(WKDateTimePickerViewController *)dateTimePickerViewController
    277471{
    278472#if USE(UICONTEXTMENU)
     
    281475}
    282476
    283 - (NSString *)calendarType
    284 {
    285     return [_datePicker calendar].calendarIdentifier;
    286 }
    287 
    288 - (double)hour
    289 {
    290     NSCalendar *calendar = [NSCalendar currentCalendar];
    291     NSDateComponents *components = [calendar components:NSCalendarUnitHour fromDate:[_datePicker date]];
    292 
    293     return [components hour];
    294 }
    295 
    296 - (double)minute
    297 {
    298     NSCalendar *calendar = [NSCalendar currentCalendar];
    299     NSDateComponents *components = [calendar components:NSCalendarUnitMinute fromDate:[_datePicker date]];
    300 
    301     return [components minute];
     477- (BOOL)shouldForceGregorianCalendar
     478{
     479    auto autofillFieldName = _view.focusedElementInformation.autofillFieldName;
     480    return autofillFieldName == WebCore::AutofillFieldName::CcExpMonth
     481        || autofillFieldName == WebCore::AutofillFieldName::CcExp
     482        || autofillFieldName == WebCore::AutofillFieldName::CcExpYear;
    302483}
    303484
    304485- (void)dealloc
    305486{
    306     [_datePicker removeTarget:self action:NULL forControlEvents:UIControlEventValueChanged];
    307487#if USE(UICONTEXTMENU)
    308488    [self removeContextMenuInteraction];
     
    311491}
    312492
    313 - (BOOL)shouldPresentGregorianCalendar:(const WebKit::FocusedElementInformation&)nodeInfo
    314 {
    315     return nodeInfo.autofillFieldName == WebCore::AutofillFieldName::CcExpMonth
    316         || nodeInfo.autofillFieldName == WebCore::AutofillFieldName::CcExp
    317         || nodeInfo.autofillFieldName == WebCore::AutofillFieldName::CcExpYear;
    318 }
    319 
    320 - (UIView *)controlView
    321 {
    322     return _datePicker.get();
    323 }
    324 
    325493- (NSInteger)_timeZoneOffsetFromGMT:(NSDate *)date
    326494{
     
    328496        return 0;
    329497
    330     return [_datePicker.get().timeZone secondsFromGMTForDate:date];
     498    return [[_dateTimePickerViewController timeZone] secondsFromGMTForDate:date];
    331499}
    332500
     
    335503    // The "time" input type may have seconds and milliseconds information which we
    336504    // just ignore. For example: "01:56:20.391" is shortened to just "01:56".
    337     if (_isTimeInput)
     505    if (_view.focusedElementInformation.elementType == WebKit::InputType::Time)
    338506        return [value substringToIndex:[kTimeFormatString length]];
    339507
    340508    return value;
    341 }
    342 
    343 - (void)_dateChangedSetAsNumber
    344 {
    345     NSDate *date = [_datePicker date];
    346     [_view updateFocusedElementValueAsNumber:([date timeIntervalSince1970] + [self _timeZoneOffsetFromGMT:date]) * kMillisecondsPerSecond];
    347509}
    348510
     
    351513    RetainPtr<NSLocale> englishLocale = adoptNS([[NSLocale alloc] initWithLocaleIdentifier:@"en_US_POSIX"]);
    352514    RetainPtr<NSDateFormatter> dateFormatter = adoptNS([[NSDateFormatter alloc] init]);
    353     [dateFormatter setTimeZone:_datePicker.get().timeZone];
     515    [dateFormatter setTimeZone:[_dateTimePickerViewController timeZone]];
    354516    [dateFormatter setDateFormat:_formatString];
    355517    [dateFormatter setLocale:englishLocale.get()];
     
    357519}
    358520
     521- (void)_dateChangedSetAsNumber
     522{
     523    NSDate *date = [_dateTimePickerViewController date];
     524    [_view updateFocusedElementValueAsNumber:(date.timeIntervalSince1970 + [self _timeZoneOffsetFromGMT:date]) * kMillisecondsPerSecond];
     525}
     526
    359527- (void)_dateChangedSetAsString
    360528{
    361529    // Force English locale because that is what HTML5 value parsing expects.
    362530    RetainPtr<NSDateFormatter> dateFormatter = [self dateFormatterForPicker];
    363     [_view updateFocusedElementValue:[dateFormatter stringFromDate:[_datePicker date]]];
     531    [_view updateFocusedElementValue:[dateFormatter stringFromDate:[_dateTimePickerViewController date]]];
    364532}
    365533
     
    376544}
    377545
    378 - (void)_dateChangeHandler:(id)sender
    379 {
    380     [self _dateChanged];
    381 }
    382 
    383546- (void)setDateTimePickerToInitialValue
    384547{
    385548    if ([_initialValue isEqual: @""]) {
    386         [_datePicker setDate:[NSDate date]];
     549        [_dateTimePickerViewController setDate:[NSDate date]];
    387550        [self _dateChanged];
    388551    } else if (_formatString) {
     
    390553        RetainPtr<NSDateFormatter> dateFormatter = [self dateFormatterForPicker];
    391554        NSDate *parsedDate = [dateFormatter dateFromString:[self _sanitizeInputValueForFormatter:_initialValue.get()]];
    392         [_datePicker setDate:parsedDate ? parsedDate : [NSDate date]];
     555        [_dateTimePickerViewController setDate:parsedDate ? parsedDate : [NSDate date]];
    393556    } else {
    394557        // Convert the number value to a date object for the fields affected by timezones.
     
    396559        NSInteger timeZoneOffset = [self _timeZoneOffsetFromGMT:[NSDate dateWithTimeIntervalSince1970:secondsSince1970]];
    397560        NSTimeInterval adjustedSecondsSince1970 = secondsSince1970 - timeZoneOffset;
    398         [_datePicker setDate:[NSDate dateWithTimeIntervalSince1970:adjustedSecondsSince1970]];
    399     }
     561        [_dateTimePickerViewController setDate:[NSDate dateWithTimeIntervalSince1970:adjustedSecondsSince1970]];
     562    }
     563}
     564
     565- (UIView *)controlView
     566{
     567    return nil;
    400568}
    401569
    402570- (void)controlBeginEditing
    403571{
    404     if (_presenting)
    405         return;
    406 
    407     _presenting = YES;
    408 
    409572    auto elementType = _view.focusedElementInformation.elementType;
    410573    if (elementType == WebKit::InputType::Time || elementType == WebKit::InputType::DateTimeLocal)
     
    412575
    413576    // Set the time zone in case it changed.
    414     _datePicker.get().timeZone = [NSTimeZone localTimeZone];
     577    [_dateTimePickerViewController setTimeZone:NSTimeZone.localTimeZone];
    415578
    416579    // Currently no value for the <input>. Start the picker with the current time.
     
    425588}
    426589
    427 - (void)setHour:(NSInteger)hour minute:(NSInteger)minute
    428 {
    429     NSString *timeString = [NSString stringWithFormat:@"%.2ld:%.2ld", (long)hour, (long)minute];
    430     [_datePicker setDate:[[self dateFormatterForPicker] dateFromString:timeString]];
    431     [self _dateChanged];
    432 }
    433 
    434 - (WKDateTimeContextMenuViewController *)viewController
    435 {
    436     return _viewController.get();
    437 }
    438 
    439590- (void)controlEndEditing
    440591{
    441     _presenting = NO;
    442 
    443592    [_view stopRelinquishingFirstResponderToFocusedElement];
    444593
     
    447596#endif
    448597}
     598
     599- (NSString *)calendarType
     600{
     601    return [_dateTimePickerViewController calendar].calendarIdentifier;
     602}
     603
     604- (double)hour
     605{
     606    NSCalendar *calendar = [NSCalendar currentCalendar];
     607    NSDateComponents *components = [calendar components:NSCalendarUnitHour fromDate:[_dateTimePickerViewController date]];
     608
     609    return components.hour;
     610}
     611
     612- (double)minute
     613{
     614    NSCalendar *calendar = [NSCalendar currentCalendar];
     615    NSDateComponents *components = [calendar components:NSCalendarUnitMinute fromDate:[_dateTimePickerViewController date]];
     616
     617    return components.minute;
     618}
     619
     620- (void)setHour:(NSInteger)hour minute:(NSInteger)minute
     621{
     622    NSString *timeString = [NSString stringWithFormat:@"%.2ld:%.2ld", (long)hour, (long)minute];
     623    [_dateTimePickerViewController setDate:[[self dateFormatterForPicker] dateFromString:timeString]];
     624    [self _dateChanged];
     625}
     626
    449627@end
    450628
Note: See TracChangeset for help on using the changeset viewer.