Changeset 261815 in webkit


Ignore:
Timestamp:
May 18, 2020 9:23:30 AM (4 years ago)
Author:
Wenson Hsieh
Message:

Single selection <select> with <optgroups> shows multiple selected options
https://bugs.webkit.org/show_bug.cgi?id=199485
<rdar://problem/52757531>

Reviewed by Megan Gardner.

Source/WebKit:

Fixes a long-standing bug in WKMultipleSelectPicker. Prior to this patch, we rely on the delegate method
-pickerView:row:column:checked: to be called twice whenever an item is selected: one time for the item that is
no longer checked, and another for the newly checked item. This method is responsible for updating the cached
FocusedElementInformation that determines the data model for the select menu, with the expectation that the
unchecked item would be updated to have isSelected = false;, and the new checked item would have isSelected
= true;.

However, -pickerView:row:column:checked: is only called for visible item cells. This means that if the user
checks an item, scrolls the select menu items down so that the checked item is offscreen, and then checks a
different item, we only get notified that the new item is checked, and as a result, fail to uncheck the previous
item.

To address this, tweak our logic for handling a single select so that when an item is checked, we additionally
update the previously checked item to not be selected. Also, fix what seems to be a bug in the logic for
updating _singleSelectionIndex, which is currently updated even when the item is unchecked. It seems to work
out at the moment, because -pickerView:row:column:checked: seems to be called with checked := YES after the
previous item was unchecked (assuming that it was visible).

Test: fast/forms/ios/no-stale-checked-items-in-select-picker.html

  • UIProcess/API/ios/WKWebViewPrivateForTestingIOS.h:
  • UIProcess/API/ios/WKWebViewTestingIOS.mm:

(-[WKWebView selectFormAccessoryHasCheckedItemAtRow:]):

  • UIProcess/ios/WKContentViewInteraction.h:
  • UIProcess/ios/WKContentViewInteraction.mm:

(-[WKContentView selectFormAccessoryHasCheckedItemAtRow:]):

Add plumbing for a new testing hook.

  • UIProcess/ios/forms/WKFormSelectControl.h:
  • UIProcess/ios/forms/WKFormSelectControl.mm:

(-[WKFormSelectControl selectFormAccessoryHasCheckedItemAtRow:]):

  • UIProcess/ios/forms/WKFormSelectPicker.mm:

(-[WKMultipleSelectPicker pickerView:viewForRow:forComponent:reusingView:]):
(-[WKMultipleSelectPicker pickerView:row:column:checked:]):
(-[WKMultipleSelectPicker selectRow:inComponent:extendingSelection:]):

Also, fix an existing bug in this testing helper method that crashed the test runner due to calling an
unimplemented selector. Instead of trying to invoke -pickerView:didSelectRow:inComponent:, we should be using
-pickerView:row:column:checked: instead for multiple select pickers (which, somewhat confusingly, are still
used for single select elements that have optgroups.)

(-[WKMultipleSelectPicker selectFormAccessoryHasCheckedItemAtRow:]):

Tools:

Add a new helper method to check whether the currently presented form accessory is a select menu, and has a
checked menu item at the given row.

  • TestRunnerShared/UIScriptContext/Bindings/UIScriptController.idl:
  • TestRunnerShared/UIScriptContext/UIScriptController.h:

(WTR::UIScriptController::selectFormAccessoryHasCheckedItemAtRow const):

  • WebKitTestRunner/ios/UIScriptControllerIOS.h:
  • WebKitTestRunner/ios/UIScriptControllerIOS.mm:

(WTR::UIScriptControllerIOS::selectFormAccessoryHasCheckedItemAtRow const):

LayoutTests:

Add a layout test to verify that we don't leave behind a checked select item after scrolling it offscreen and
then checking a different item.

  • fast/forms/ios/no-stale-checked-items-in-select-picker-expected.txt: Added.
  • fast/forms/ios/no-stale-checked-items-in-select-picker.html: Added.
  • platform/ipad/TestExpectations:
  • resources/ui-helper.js:

(window.UIHelper.selectFormAccessoryPickerRow):
(window.UIHelper.selectFormAccessoryHasCheckedItemAtRow):

Location:
trunk
Files:
2 added
16 edited

Legend:

Unmodified
Added
Removed
  • trunk/LayoutTests/ChangeLog

    r261813 r261815  
     12020-05-18  Wenson Hsieh  <wenson_hsieh@apple.com>
     2
     3        Single selection <select> with <optgroups> shows multiple selected options
     4        https://bugs.webkit.org/show_bug.cgi?id=199485
     5        <rdar://problem/52757531>
     6
     7        Reviewed by Megan Gardner.
     8
     9        Add a layout test to verify that we don't leave behind a checked select item after scrolling it offscreen and
     10        then checking a different item.
     11
     12        * fast/forms/ios/no-stale-checked-items-in-select-picker-expected.txt: Added.
     13        * fast/forms/ios/no-stale-checked-items-in-select-picker.html: Added.
     14        * platform/ipad/TestExpectations:
     15        * resources/ui-helper.js:
     16        (window.UIHelper.selectFormAccessoryPickerRow):
     17        (window.UIHelper.selectFormAccessoryHasCheckedItemAtRow):
     18
    1192020-05-18  Youenn Fablet  <youenn@apple.com>
    220
  • trunk/LayoutTests/platform/ipad/TestExpectations

    r259782 r261815  
    88fast/forms/ios/user-scalable-scales-for-keyboard-focus-with-no-author-defined-scale.html [ Skip ]
    99fast/visual-viewport/ios/caret-after-focus-in-fixed.html [ Skip ]
     10
     11# The select picker input view is not displayed on iPad.
     12fast/forms/ios/no-stale-checked-items-in-select-picker.html
    1013
    1114# These tests are designed for iPhone and crash on iPad
  • trunk/LayoutTests/resources/ui-helper.js

    r261812 r261815  
    723723    static selectFormAccessoryPickerRow(rowIndex)
    724724    {
    725         const selectRowScript = `(() => uiController.selectFormAccessoryPickerRow(${rowIndex}))()`;
     725        const selectRowScript = `uiController.selectFormAccessoryPickerRow(${rowIndex})`;
    726726        return new Promise(resolve => testRunner.runUIScript(selectRowScript, resolve));
     727    }
     728
     729    static selectFormAccessoryHasCheckedItemAtRow(rowIndex)
     730    {
     731        return new Promise(resolve => testRunner.runUIScript(`uiController.selectFormAccessoryHasCheckedItemAtRow(${rowIndex})`, result => {
     732            resolve(result === "true");
     733        }));
    727734    }
    728735
  • trunk/Source/WebKit/ChangeLog

    r261812 r261815  
     12020-05-18  Wenson Hsieh  <wenson_hsieh@apple.com>
     2
     3        Single selection <select> with <optgroups> shows multiple selected options
     4        https://bugs.webkit.org/show_bug.cgi?id=199485
     5        <rdar://problem/52757531>
     6
     7        Reviewed by Megan Gardner.
     8
     9        Fixes a long-standing bug in WKMultipleSelectPicker. Prior to this patch, we rely on the delegate method
     10        `-pickerView:row:column:checked:` to be called twice whenever an item is selected: one time for the item that is
     11        no longer checked, and another for the newly checked item. This method is responsible for updating the cached
     12        `FocusedElementInformation` that determines the data model for the select menu, with the expectation that the
     13        unchecked item would be updated to have `isSelected = false;`, and the new checked item would have `isSelected`
     14        `= true;`.
     15
     16        However, `-pickerView:row:column:checked:` is only called for visible item cells. This means that if the user
     17        checks an item, scrolls the select menu items down so that the checked item is offscreen, and then checks a
     18        different item, we only get notified that the new item is checked, and as a result, fail to uncheck the previous
     19        item.
     20
     21        To address this, tweak our logic for handling a single select so that when an item is checked, we additionally
     22        update the previously checked item to not be selected. Also, fix what seems to be a bug in the logic for
     23        updating `_singleSelectionIndex`, which is currently updated even when the item is unchecked. It seems to work
     24        out at the moment, because `-pickerView:row:column:checked:` seems to be called with `checked := YES` after the
     25        previous item was unchecked (assuming that it was visible).
     26
     27        Test: fast/forms/ios/no-stale-checked-items-in-select-picker.html
     28
     29        * UIProcess/API/ios/WKWebViewPrivateForTestingIOS.h:
     30        * UIProcess/API/ios/WKWebViewTestingIOS.mm:
     31        (-[WKWebView selectFormAccessoryHasCheckedItemAtRow:]):
     32        * UIProcess/ios/WKContentViewInteraction.h:
     33        * UIProcess/ios/WKContentViewInteraction.mm:
     34        (-[WKContentView selectFormAccessoryHasCheckedItemAtRow:]):
     35
     36        Add plumbing for a new testing hook.
     37
     38        * UIProcess/ios/forms/WKFormSelectControl.h:
     39        * UIProcess/ios/forms/WKFormSelectControl.mm:
     40        (-[WKFormSelectControl selectFormAccessoryHasCheckedItemAtRow:]):
     41        * UIProcess/ios/forms/WKFormSelectPicker.mm:
     42        (-[WKMultipleSelectPicker pickerView:viewForRow:forComponent:reusingView:]):
     43        (-[WKMultipleSelectPicker pickerView:row:column:checked:]):
     44        (-[WKMultipleSelectPicker selectRow:inComponent:extendingSelection:]):
     45
     46        Also, fix an existing bug in this testing helper method that crashed the test runner due to calling an
     47        unimplemented selector. Instead of trying to invoke `-pickerView:didSelectRow:inComponent:`, we should be using
     48        `-pickerView:row:column:checked:` instead for multiple select pickers (which, somewhat confusingly, are still
     49        used for single select elements that have `optgroup`s.)
     50
     51        (-[WKMultipleSelectPicker selectFormAccessoryHasCheckedItemAtRow:]):
     52
    1532020-05-18  Wenson Hsieh  <wenson_hsieh@apple.com>
    254
  • trunk/Source/WebKit/UIProcess/API/ios/WKWebViewPrivateForTestingIOS.h

    r261638 r261815  
    4949- (void)_dismissFilePicker;
    5050- (void)selectFormAccessoryPickerRow:(int)rowIndex;
     51- (BOOL)selectFormAccessoryHasCheckedItemAtRow:(long)rowIndex;
    5152
    5253- (BOOL)_mayContainEditableElementsInRect:(CGRect)rect;
  • trunk/Source/WebKit/UIProcess/API/ios/WKWebViewTestingIOS.mm

    r261638 r261815  
    145145}
    146146
     147- (BOOL)selectFormAccessoryHasCheckedItemAtRow:(long)rowIndex
     148{
     149    return [_contentView selectFormAccessoryHasCheckedItemAtRow:rowIndex];
     150}
     151
    147152- (NSString *)selectFormPopoverTitle
    148153{
  • trunk/Source/WebKit/UIProcess/ios/WKContentViewInteraction.h

    r261658 r261815  
    620620- (void)_simulateTextEntered:(NSString *)text;
    621621- (void)selectFormAccessoryPickerRow:(NSInteger)rowIndex;
     622- (BOOL)selectFormAccessoryHasCheckedItemAtRow:(long)rowIndex;
    622623- (void)setTimePickerValueToHour:(NSInteger)hour minute:(NSInteger)minute;
    623624- (double)timePickerValueHour;
  • trunk/Source/WebKit/UIProcess/ios/WKContentViewInteraction.mm

    r261812 r261815  
    87588758}
    87598759
     8760- (BOOL)selectFormAccessoryHasCheckedItemAtRow:(long)rowIndex
     8761{
     8762#if !PLATFORM(WATCHOS)
     8763    if ([_inputPeripheral isKindOfClass:[WKFormSelectControl self]])
     8764        return [(WKFormSelectControl *)_inputPeripheral selectFormAccessoryHasCheckedItemAtRow:rowIndex];
     8765#endif
     8766    return NO;
     8767}
     8768
    87608769- (NSString *)textContentTypeForTesting
    87618770{
  • trunk/Source/WebKit/UIProcess/ios/forms/WKFormSelectControl.h

    r244955 r261815  
    4242@interface WKFormSelectControl(WKTesting)
    4343- (void)selectRow:(NSInteger)rowIndex inComponent:(NSInteger)componentIndex extendingSelection:(BOOL)extendingSelection;
     44- (BOOL)selectFormAccessoryHasCheckedItemAtRow:(long)rowIndex;
    4445@property (nonatomic, readonly) NSString *selectFormPopoverTitle;
    4546@end
     
    4849@optional
    4950- (void)selectRow:(NSInteger)rowIndex inComponent:(NSInteger)componentIndex extendingSelection:(BOOL)extendingSelection;
     51- (BOOL)selectFormAccessoryHasCheckedItemAtRow:(long)rowIndex;
    5052@end
    5153
  • trunk/Source/WebKit/UIProcess/ios/forms/WKFormSelectControl.mm

    r253661 r261815  
    102102}
    103103
     104- (BOOL)selectFormAccessoryHasCheckedItemAtRow:(long)rowIndex
     105{
     106    return [self.control respondsToSelector:@selector(selectFormAccessoryHasCheckedItemAtRow:)]
     107        && [id<WKSelectTesting>(self.control) selectFormAccessoryHasCheckedItemAtRow:rowIndex];
     108}
     109
    104110@end
    105111
  • trunk/Source/WebKit/UIProcess/ios/forms/WKFormSelectPicker.mm

    r245272 r261815  
    220220- (UIView *)pickerView:(UIPickerView *)pickerView viewForRow:(NSInteger)rowIndex forComponent:(NSInteger)columnIndex reusingView:(UIView *)view
    221221{
    222     const OptionItem& item = [_view focusedSelectElementOptions][rowIndex];
    223     UIPickerContentView* pickerItem = item.isGroup ? [[[WKOptionGroupPickerCell alloc] initWithOptionItem:item] autorelease] : [[[WKOptionPickerCell alloc] initWithOptionItem:item] autorelease];
     222    auto& item = [_view focusedSelectElementOptions][rowIndex];
     223    UIPickerContentView *pickerItem = item.isGroup ? [[[WKOptionGroupPickerCell alloc] initWithOptionItem:item] autorelease] : [[[WKOptionPickerCell alloc] initWithOptionItem:item] autorelease];
    224224
    225225    // The cell starts out with a null frame. We need to set its frame now so we can find the right font size.
     
    273273- (void)pickerView:(UIPickerView *)pickerView row:(int)rowIndex column:(int)columnIndex checked:(BOOL)isChecked
    274274{
    275     if ((size_t)rowIndex >= [_view focusedSelectElementOptions].size())
     275    auto numberOfOptions = static_cast<NSUInteger>([_view focusedSelectElementOptions].size());
     276    if (numberOfOptions <= static_cast<NSUInteger>(rowIndex))
    276277        return;
    277278
    278     OptionItem& item = [_view focusedSelectElementOptions][rowIndex];
     279    auto& item = [_view focusedSelectElementOptions][rowIndex];
    279280
    280281    // FIXME: Remove this workaround once <rdar://problem/18745253> is fixed.
     
    292293        [_view page]->setFocusedElementSelectedIndex([self findItemIndexAt:rowIndex], true);
    293294        item.isSelected = isChecked;
    294     } else {
     295    } else if (isChecked) {
    295296        // Single selection.
    296         item.isSelected = NO;
     297        if (_singleSelectionIndex < numberOfOptions)
     298            [_view focusedSelectElementOptions][_singleSelectionIndex].isSelected = false;
     299
    297300        _singleSelectionIndex = rowIndex;
    298301
    299302        // This private delegate often gets called for multiple rows in the picker,
    300303        // so we only activate and set as selected the checked item in single selection.
    301         if (isChecked) {
    302             [_view page]->setFocusedElementSelectedIndex([self findItemIndexAt:rowIndex]);
    303             item.isSelected = YES;
    304         }
    305     }
     304        [_view page]->setFocusedElementSelectedIndex([self findItemIndexAt:rowIndex]);
     305        item.isSelected = true;
     306    } else
     307        item.isSelected = false;
    306308}
    307309
     
    312314    [self selectRow:rowIndex inComponent:0 animated:NO];
    313315    // Progammatic selection changes don't call the delegate, so do that manually.
    314     [self.delegate pickerView:self didSelectRow:rowIndex inComponent:0];
     316    [self pickerView:self row:rowIndex column:0 checked:YES];
     317}
     318
     319- (BOOL)selectFormAccessoryHasCheckedItemAtRow:(long)rowIndex
     320{
     321    auto numberOfRows = [self numberOfRowsInComponent:0];
     322    if (rowIndex >= numberOfRows)
     323        return NO;
     324
     325    return [(UIPickerContentView *)[self viewForRow:rowIndex forComponent:0] isChecked];
    315326}
    316327
  • trunk/Tools/ChangeLog

    r261812 r261815  
     12020-05-18  Wenson Hsieh  <wenson_hsieh@apple.com>
     2
     3        Single selection <select> with <optgroups> shows multiple selected options
     4        https://bugs.webkit.org/show_bug.cgi?id=199485
     5        <rdar://problem/52757531>
     6
     7        Reviewed by Megan Gardner.
     8
     9        Add a new helper method to check whether the currently presented form accessory is a select menu, and has a
     10        checked menu item at the given row.
     11
     12        * TestRunnerShared/UIScriptContext/Bindings/UIScriptController.idl:
     13        * TestRunnerShared/UIScriptContext/UIScriptController.h:
     14        (WTR::UIScriptController::selectFormAccessoryHasCheckedItemAtRow const):
     15        * WebKitTestRunner/ios/UIScriptControllerIOS.h:
     16        * WebKitTestRunner/ios/UIScriptControllerIOS.mm:
     17        (WTR::UIScriptControllerIOS::selectFormAccessoryHasCheckedItemAtRow const):
     18
    1192020-05-18  Wenson Hsieh  <wenson_hsieh@apple.com>
    220
  • trunk/Tools/TestRunnerShared/UIScriptContext/Bindings/UIScriptController.idl

    r261812 r261815  
    213213    // <select> picker
    214214    void selectFormAccessoryPickerRow(long rowIndex);
     215    boolean selectFormAccessoryHasCheckedItemAtRow(long rowIndex);
    215216    readonly attribute DOMString selectFormPopoverTitle;
    216217    readonly attribute DOMString formInputLabel;
  • trunk/Tools/TestRunnerShared/UIScriptContext/UIScriptController.h

    r261812 r261815  
    206206    virtual void dismissFormAccessoryView() { notImplemented(); }
    207207    virtual void selectFormAccessoryPickerRow(long) { notImplemented(); }
     208    virtual bool selectFormAccessoryHasCheckedItemAtRow(long) const { return false; }
    208209    virtual JSRetainPtr<JSStringRef> textContentType() const { notImplemented(); return nullptr; }
    209210    virtual JSRetainPtr<JSStringRef> selectFormPopoverTitle() const { notImplemented(); return nullptr; }
  • trunk/Tools/WebKitTestRunner/ios/UIScriptControllerIOS.h

    r261812 r261815  
    8989    JSRetainPtr<JSStringRef> formInputLabel() const override;
    9090    void selectFormAccessoryPickerRow(long rowIndex) override;
     91    bool selectFormAccessoryHasCheckedItemAtRow(long rowIndex) const override;
    9192    void setTimePickerValue(long hour, long minute) override;
    9293    double timePickerValueHour() const override;
  • trunk/Tools/WebKitTestRunner/ios/UIScriptControllerIOS.mm

    r261812 r261815  
    607607}
    608608
     609bool UIScriptControllerIOS::selectFormAccessoryHasCheckedItemAtRow(long rowIndex) const
     610{
     611    return [webView() selectFormAccessoryHasCheckedItemAtRow:rowIndex];
     612}
     613
    609614void UIScriptControllerIOS::setTimePickerValue(long hour, long minute)
    610615{
Note: See TracChangeset for help on using the changeset viewer.