RESOLVED FIXED 221594
[iOS] Add support for the language/subtitle tracks button using `UIMenu`
https://bugs.webkit.org/show_bug.cgi?id=221594
Summary [iOS] Add support for the language/subtitle tracks button using `UIMenu`
Devin Rousso
Reported 2021-02-08 22:03:20 PST
instead of `TracksPanel` like on macOS (which would be possible but difficult to match the exact behavior/style of UIKit) just use a native `UIContextMenuInteraction` and `UIMenu`
Attachments
Patch (168.41 KB, patch)
2021-02-08 22:29 PST, Devin Rousso
no flags
Patch (168.41 KB, patch)
2021-02-08 23:18 PST, Devin Rousso
no flags
Patch (169.05 KB, patch)
2021-02-09 12:20 PST, Devin Rousso
no flags
Patch (171.26 KB, patch)
2021-02-09 15:42 PST, Devin Rousso
no flags
Radar WebKit Bug Importer
Comment 1 2021-02-08 22:03:32 PST
Devin Rousso
Comment 2 2021-02-08 22:29:12 PST
Devin Rousso
Comment 3 2021-02-08 23:18:53 PST
Eric Carlson
Comment 4 2021-02-09 10:05:20 PST
Comment on attachment 419680 [details] Patch View in context: https://bugs.webkit.org/attachment.cgi?id=419680&action=review The WebCore changes look good to me modulo my minor comments, but someone that knows more about WKActionSheetAssistant than I do should review those changes. > Source/WebCore/Modules/mediacontrols/MediaControlsHost.cpp:327 > + > + HashMap<MediaControlsContextMenuItem::ID, Variant<RefPtr<AudioTrack>, RefPtr<TextTrack>>> idMap; Nit: something like `using MenuTrackItem = Variant<RefPtr<AudioTrack>, RefPtr<TextTrack>>` would make this easier to parse. > Source/WebCore/Modules/mediacontrols/MediaControlsHost.cpp:407 > + auto selectedItem = idMap.get(selectedItemID); Might be worth adding something like `ASSERT(selectedItem != HashTraits<MediaControlsContextMenuItem::ID>::emptyValue())` > Source/WebCore/Modules/mediacontrols/MediaControlsHost.cpp:419 > + for (auto& track : idMap.values()) { > + if (auto* textTrack = WTF::get_if<RefPtr<TextTrack>>(track)) > + (*textTrack)->setMode(TextTrack::Mode::Disabled); > + } Doesn't HTMLMediaElement::setSelectedTextTrack already take care of this? > Source/WebCore/Modules/mediacontrols/MediaControlsHost.idl:68 > + [Conditional=MEDIA_CONTROLS_CONTEXT_MENUS, EnabledBySetting=MediaControlsContextMenus] undefined showMediaControlsContextMenu(HTMLElement target, ContextMenuOptions options); Does this need `EnabledAtRuntime=ModernMediaControls`, or is that obsolete (and so should be removed everywhere)? > Source/WebCore/page/MediaControlsContextMenuItem.h:48 > +struct MediaControlsContextMenuItem { > + using ID = uint64_t; > + static constexpr ID invalidID = 0; > + > + ID id { invalidID }; > + String title; > + String icon; > + bool isChecked { false }; > + Vector<MediaControlsContextMenuItem> children; > + > + template<class Encoder> void encode(Encoder&) const; > + template<class Decoder> static Optional<MediaControlsContextMenuItem> decode(Decoder&); > +}; Isn't `children` always empty except for the last entry? It should either be Optional<> or you could define one struct for the menu and another for the items? > Source/WebKit/UIProcess/ios/WKActionSheetAssistant.mm:1077 > +- (NSArray *)currentAvailableMediaControlsContextMenuItems s/currentAvailableMediaControlsContextMenuItems/currentlyAvailableMediaControlsContextMenuItems/
Devin Rousso
Comment 5 2021-02-09 11:27:28 PST
Comment on attachment 419680 [details] Patch View in context: https://bugs.webkit.org/attachment.cgi?id=419680&action=review >> Source/WebCore/Modules/mediacontrols/MediaControlsHost.cpp:327 >> + HashMap<MediaControlsContextMenuItem::ID, Variant<RefPtr<AudioTrack>, RefPtr<TextTrack>>> idMap; > > Nit: something like `using MenuTrackItem = Variant<RefPtr<AudioTrack>, RefPtr<TextTrack>>` would make this easier to parse. ooo good idea =D >> Source/WebCore/Modules/mediacontrols/MediaControlsHost.cpp:407 >> + auto selectedItem = idMap.get(selectedItemID); > > Might be worth adding something like `ASSERT(selectedItem != HashTraits<MediaControlsContextMenuItem::ID>::emptyValue())` I think `HashTable::checkKey` already does this >> Source/WebCore/Modules/mediacontrols/MediaControlsHost.cpp:419 >> + } > > Doesn't HTMLMediaElement::setSelectedTextTrack already take care of this? I was emulating the code inside `TrackSupport.prototype.tracksPanelSelectionDidChange`. AFAICT `HTMLMediaElement::setSelectedTextTrack` doesn't do this if the given `trackToSelect` is the special "automatic" or "off" track. >> Source/WebCore/Modules/mediacontrols/MediaControlsHost.idl:68 >> + [Conditional=MEDIA_CONTROLS_CONTEXT_MENUS, EnabledBySetting=MediaControlsContextMenus] undefined showMediaControlsContextMenu(HTMLElement target, ContextMenuOptions options); > > Does this need `EnabledAtRuntime=ModernMediaControls`, or is that obsolete (and so should be removed everywhere)? Oh yeah good call. I'll add that as well :) >> Source/WebCore/page/MediaControlsContextMenuItem.h:48 >> +}; > > Isn't `children` always empty except for the last entry? It should either be Optional<> or you could define one struct for the menu and another for the items? The idea here is to be able to build a tree. If a video has both audio and text tracks, this would be used to create two submenus that each have children. A couple of the LayoutTests I wrote dump this structure so you can see it. IMO `Optional<Vector<...>>` is kinda redundant in this case because if a menu item has children it's not selectable (at the very least I think this is the case for macOS/iOS menus). Yes I could add distinct subclasses for menu vs action, but I think `children.isEmpty()` is indicator enough as to whether or not it's a menu or an action and distinct subclasses would just make this code more complicated.
Wenson Hsieh
Comment 6 2021-02-09 11:56:50 PST
Comment on attachment 419680 [details] Patch View in context: https://bugs.webkit.org/attachment.cgi?id=419680&action=review >>> Source/WebCore/Modules/mediacontrols/MediaControlsHost.idl:68 >>> + [Conditional=MEDIA_CONTROLS_CONTEXT_MENUS, EnabledBySetting=MediaControlsContextMenus] undefined showMediaControlsContextMenu(HTMLElement target, ContextMenuOptions options); >> >> Does this need `EnabledAtRuntime=ModernMediaControls`, or is that obsolete (and so should be removed everywhere)? > > Oh yeah good call. I'll add that as well :) IIRC, `EnabledAtRuntime` is for runtime-enabled features (`WebCore::RuntimeEnabledFeatures`), and allows the bindings generator guards the IDL with a runtime enabled feature check: ``` if ($context->extendedAttributes->{EnabledAtRuntime}) { assert("Must specify value for EnabledAtRuntime.") if $context->extendedAttributes->{EnabledAtRuntime} eq "VALUE_IS_MISSING"; AddToImplIncludes("RuntimeEnabledFeatures.h"); my @flags = split(/&/, $context->extendedAttributes->{EnabledAtRuntime}); foreach my $flag (@flags) { push(@conjuncts, "RuntimeEnabledFeatures::sharedFeatures()." . ToMethodName($flag) . "Enabled()"); } } ``` > Source/WebKit/UIProcess/ios/WKActionSheetAssistant.mm:1041 > + NSMutableArray *array = [NSMutableArray array]; Nit - `-arrayWithCapacity:`? > Source/WebKit/WebProcess/WebPage/WebPage.cpp:7170 > + completionHandler(selectedItemID); Nit - does it work if you just pass in `WTFMove(completionHandler)` here, or is the outer completion handler needed? > Tools/WebKitTestRunner/ios/UIScriptControllerIOS.mm:1040 > + if (UIView *calloutBar = UICalloutBar.activeCalloutBar) { Nit - I need to double check, but I suspect `UICalloutBar.activeCalloutBar` is non-nil even if the callout bar is not currently shown. (The point of `activeCalloutBar`, `+[UIKeyboardImpl activeInstance]`, and the like are to avoid the cost of instantiating these singleton objects too early, usually during app launch).
Devin Rousso
Comment 7 2021-02-09 12:12:25 PST
Comment on attachment 419680 [details] Patch View in context: https://bugs.webkit.org/attachment.cgi?id=419680&action=review >> Source/WebKit/UIProcess/ios/WKActionSheetAssistant.mm:1041 >> + NSMutableArray *array = [NSMutableArray array]; > > Nit - `-arrayWithCapacity:`? This was just copypasta from above, but I'm happy to change it :) >> Source/WebKit/WebProcess/WebPage/WebPage.cpp:7170 >> + completionHandler(selectedItemID); > > Nit - does it work if you just pass in `WTFMove(completionHandler)` here, or is the outer completion handler needed? :extreme facepalm: >> Tools/WebKitTestRunner/ios/UIScriptControllerIOS.mm:1040 >> + if (UIView *calloutBar = UICalloutBar.activeCalloutBar) { > > Nit - I need to double check, but I suspect `UICalloutBar.activeCalloutBar` is non-nil even if the callout bar is not currently shown. (The point of `activeCalloutBar`, `+[UIKeyboardImpl activeInstance]`, and the like are to avoid the cost of instantiating these singleton objects too early, usually during app launch). Oh wow I completely missed that it was checking for `calloutBar.window`. Will change. Great catch!
Devin Rousso
Comment 8 2021-02-09 12:20:38 PST
Wenson Hsieh
Comment 9 2021-02-09 14:08:57 PST
Comment on attachment 419751 [details] Patch View in context: https://bugs.webkit.org/attachment.cgi?id=419751&action=review The test harness and context menu changes look good to me. I'm not familiar with the media and media-controls side of things though, so perhaps one of the media folks could take a closer look! > Source/WebKit/UIProcess/ios/WKActionSheetAssistant.mm:685 > - (void)ensureContextMenuInteraction Nit - should this become `ensureContextMenuInteractions`? (Or perhaps split this into two ensure methods, since there's no need to create and add `_dataDetectorContextMenuInteraction` when showing the media context menu). If you do decide to split this into two ensures, you could also just return the interaction from them, and then do `[self.ensureMediaControlsContextMenuInteraction _presentMenuAtLocation:_mediaControlsContextMenuTargetFrame.center()];` below. > Source/WebKit/UIProcess/ios/WKActionSheetAssistant.mm:1071 > + result[@"checked"] = [NSNumber numberWithBool:YES]; Nit - this can just be @YES.
Devin Rousso
Comment 10 2021-02-09 14:54:42 PST
Comment on attachment 419751 [details] Patch View in context: https://bugs.webkit.org/attachment.cgi?id=419751&action=review >> Source/WebKit/UIProcess/ios/WKActionSheetAssistant.mm:685 >> - (void)ensureContextMenuInteraction > > Nit - should this become `ensureContextMenuInteractions`? (Or perhaps split this into two ensure methods, since there's no need to create and add `_dataDetectorContextMenuInteraction` when showing the media context menu). > > If you do decide to split this into two ensures, you could also just return the interaction from them, and then do `[self.ensureMediaControlsContextMenuInteraction _presentMenuAtLocation:_mediaControlsContextMenuTargetFrame.center()];` below. oh yeah good idea
Devin Rousso
Comment 11 2021-02-09 15:42:16 PST
Created attachment 419777 [details] Patch also split `removeContextMenuInteraction` and move it outside of the lambda so that if a second request to show the contextmenu comes in while the first one is dismissing then it's not removed
EWS
Comment 12 2021-02-09 18:06:08 PST
Committed r272630: <https://commits.webkit.org/r272630> All reviewed patches have been landed. Closing bug and clearing flags on attachment 419777 [details].
Devin Rousso
Comment 13 2021-02-09 18:25:16 PST
Note You need to log in before you can comment on or make changes to this bug.