summaryrefslogtreecommitdiffstats
path: root/widget/cocoa/nsTouchBar.mm
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 09:22:09 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 09:22:09 +0000
commit43a97878ce14b72f0981164f87f2e35e14151312 (patch)
tree620249daf56c0258faa40cbdcf9cfba06de2a846 /widget/cocoa/nsTouchBar.mm
parentInitial commit. (diff)
downloadfirefox-upstream.tar.xz
firefox-upstream.zip
Adding upstream version 110.0.1.upstream/110.0.1upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'widget/cocoa/nsTouchBar.mm')
-rw-r--r--widget/cocoa/nsTouchBar.mm604
1 files changed, 604 insertions, 0 deletions
diff --git a/widget/cocoa/nsTouchBar.mm b/widget/cocoa/nsTouchBar.mm
new file mode 100644
index 0000000000..98c2344395
--- /dev/null
+++ b/widget/cocoa/nsTouchBar.mm
@@ -0,0 +1,604 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsTouchBar.h"
+
+#include <objc/runtime.h>
+
+#include "mozilla/MacStringHelpers.h"
+#include "nsArrayUtils.h"
+#include "nsCocoaUtils.h"
+#include "nsDirectoryServiceDefs.h"
+#include "nsIArray.h"
+#include "nsTouchBarInputIcon.h"
+#include "nsWidgetsCID.h"
+
+@implementation nsTouchBar
+
+// Used to tie action strings to buttons.
+static char sIdentifierAssociationKey;
+
+// The default space between inputs, used where layout is not automatic.
+static const uint32_t kInputSpacing = 8;
+// The width of buttons in Apple's Share ScrollView. We use this in our
+// ScrollViews to give them a native appearance.
+static const uint32_t kScrollViewButtonWidth = 144;
+static const uint32_t kInputIconSize = 16;
+
+// The system default width for Touch Bar inputs is 128px. This is double.
+#define MAIN_BUTTON_WIDTH 256
+
+#pragma mark - NSTouchBarDelegate
+
+- (instancetype)init {
+ return [self initWithInputs:nil];
+}
+
+- (instancetype)initWithInputs:(NSMutableArray<TouchBarInput*>*)aInputs {
+ if ((self = [super init])) {
+ mTouchBarHelper = do_GetService(NS_TOUCHBARHELPER_CID);
+ if (!mTouchBarHelper) {
+ NS_ERROR("Unable to create Touch Bar Helper.");
+ return nil;
+ }
+
+ self.delegate = self;
+ self.mappedLayoutItems = [NSMutableDictionary dictionary];
+ self.customizationAllowedItemIdentifiers = @[];
+
+ if (!aInputs) {
+ // This customization identifier is how users' custom layouts are saved by macOS.
+ // If this changes, all users' layouts would be reset to the default layout.
+ self.customizationIdentifier =
+ [kTouchBarBaseIdentifier stringByAppendingPathExtension:@"defaultbar"];
+ nsCOMPtr<nsIArray> allItems;
+
+ nsresult rv = mTouchBarHelper->GetAllItems(getter_AddRefs(allItems));
+ if (NS_FAILED(rv) || !allItems) {
+ return nil;
+ }
+
+ uint32_t itemCount = 0;
+ allItems->GetLength(&itemCount);
+ // This is copied to self.customizationAllowedItemIdentifiers.
+ // Required since [self.mappedItems allKeys] does not preserve order.
+ // One slot is added for the spacer item.
+ NSMutableArray* orderedIdentifiers = [NSMutableArray arrayWithCapacity:itemCount + 1];
+ for (uint32_t i = 0; i < itemCount; ++i) {
+ nsCOMPtr<nsITouchBarInput> input = do_QueryElementAt(allItems, i);
+ if (!input) {
+ continue;
+ }
+
+ TouchBarInput* convertedInput;
+ NSTouchBarItemIdentifier newInputIdentifier =
+ [TouchBarInput nativeIdentifierWithXPCOM:input];
+ if (!newInputIdentifier) {
+ continue;
+ }
+
+ // If there is already an input in mappedLayoutItems with this identifier,
+ // that means updateItem fired before this initialization. The input
+ // cached by updateItem is more current, so we should use that one.
+ if (self.mappedLayoutItems[newInputIdentifier]) {
+ convertedInput = self.mappedLayoutItems[newInputIdentifier];
+ } else {
+ convertedInput = [[TouchBarInput alloc] initWithXPCOM:input];
+ // Add new input to dictionary for lookup of properties in delegate.
+ self.mappedLayoutItems[[convertedInput nativeIdentifier]] = convertedInput;
+ }
+
+ orderedIdentifiers[i] = [convertedInput nativeIdentifier];
+ }
+ [orderedIdentifiers addObject:@"NSTouchBarItemIdentifierFlexibleSpace"];
+ self.customizationAllowedItemIdentifiers = [orderedIdentifiers copy];
+
+ NSArray* defaultItemIdentifiers = @[
+ [TouchBarInput nativeIdentifierWithType:@"button" withKey:@"back"],
+ [TouchBarInput nativeIdentifierWithType:@"button" withKey:@"forward"],
+ [TouchBarInput nativeIdentifierWithType:@"button" withKey:@"reload"],
+ [TouchBarInput nativeIdentifierWithType:@"mainButton" withKey:@"open-location"],
+ [TouchBarInput nativeIdentifierWithType:@"button" withKey:@"new-tab"],
+ [TouchBarInput shareScrubberIdentifier], [TouchBarInput searchPopoverIdentifier]
+ ];
+ self.defaultItemIdentifiers = [defaultItemIdentifiers copy];
+ } else {
+ NSMutableArray* defaultItemIdentifiers = [NSMutableArray arrayWithCapacity:[aInputs count]];
+ for (TouchBarInput* input in aInputs) {
+ self.mappedLayoutItems[[input nativeIdentifier]] = input;
+ [defaultItemIdentifiers addObject:[input nativeIdentifier]];
+ }
+ self.defaultItemIdentifiers = [defaultItemIdentifiers copy];
+ }
+ }
+
+ return self;
+}
+
+- (void)dealloc {
+ for (NSTouchBarItemIdentifier identifier in self.mappedLayoutItems) {
+ NSTouchBarItem* item = [self itemForIdentifier:identifier];
+ if (!item) {
+ continue;
+ }
+ if ([item isKindOfClass:[NSPopoverTouchBarItem class]]) {
+ [(NSPopoverTouchBarItem*)item setCollapsedRepresentationImage:nil];
+ [(nsTouchBar*)[(NSPopoverTouchBarItem*)item popoverTouchBar] release];
+ } else if ([[item view] isKindOfClass:[NSScrollView class]]) {
+ [[(NSScrollView*)[item view] documentView] release];
+ [(NSScrollView*)[item view] release];
+ }
+
+ [item release];
+ }
+
+ [self.defaultItemIdentifiers release];
+ [self.customizationAllowedItemIdentifiers release];
+ [self.scrollViewButtons removeAllObjects];
+ [self.scrollViewButtons release];
+ [self.mappedLayoutItems removeAllObjects];
+ [self.mappedLayoutItems release];
+ [super dealloc];
+}
+
+- (NSTouchBarItem*)touchBar:(NSTouchBar*)aTouchBar
+ makeItemForIdentifier:(NSTouchBarItemIdentifier)aIdentifier {
+ if (!mTouchBarHelper) {
+ return nil;
+ }
+
+ TouchBarInput* input = self.mappedLayoutItems[aIdentifier];
+ if (!input) {
+ return nil;
+ }
+
+ if ([input baseType] == TouchBarInputBaseType::kScrubber) {
+ // We check the identifier rather than the baseType here as a special case.
+ if (![aIdentifier isEqualToString:[TouchBarInput shareScrubberIdentifier]]) {
+ // We're only supporting the Share scrubber for now.
+ return nil;
+ }
+ return [self makeShareScrubberForIdentifier:aIdentifier];
+ }
+
+ if ([input baseType] == TouchBarInputBaseType::kPopover) {
+ NSPopoverTouchBarItem* newPopoverItem =
+ [[NSPopoverTouchBarItem alloc] initWithIdentifier:aIdentifier];
+ [newPopoverItem setCustomizationLabel:[input title]];
+ // We initialize popoverTouchBar here because we only allow setting this
+ // property on popover creation. Updating popoverTouchBar for every update
+ // of the popover item would be very expensive.
+ newPopoverItem.popoverTouchBar = [[nsTouchBar alloc] initWithInputs:[input children]];
+ [self updatePopover:newPopoverItem withIdentifier:[input nativeIdentifier]];
+ return newPopoverItem;
+ }
+
+ // Our new item, which will be initialized depending on aIdentifier.
+ NSCustomTouchBarItem* newItem = [[NSCustomTouchBarItem alloc] initWithIdentifier:aIdentifier];
+ [newItem setCustomizationLabel:[input title]];
+
+ if ([input baseType] == TouchBarInputBaseType::kScrollView) {
+ [self updateScrollView:newItem withIdentifier:[input nativeIdentifier]];
+ return newItem;
+ } else if ([input baseType] == TouchBarInputBaseType::kLabel) {
+ NSTextField* label = [NSTextField labelWithString:@""];
+ [self updateLabel:label withIdentifier:[input nativeIdentifier]];
+ newItem.view = label;
+ return newItem;
+ }
+
+ // The cases of a button or main button require the same setup.
+ NSButton* button = [NSButton buttonWithTitle:@"" target:self action:@selector(touchBarAction:)];
+ newItem.view = button;
+
+ if ([input baseType] == TouchBarInputBaseType::kButton &&
+ ![[input type] hasPrefix:@"scrollView"]) {
+ [self updateButton:newItem withIdentifier:[input nativeIdentifier]];
+ } else if ([input baseType] == TouchBarInputBaseType::kMainButton) {
+ [self updateMainButton:newItem withIdentifier:[input nativeIdentifier]];
+ }
+ return newItem;
+}
+
+- (bool)updateItem:(TouchBarInput*)aInput {
+ if (!mTouchBarHelper) {
+ return false;
+ }
+
+ NSTouchBarItem* item = [self itemForIdentifier:[aInput nativeIdentifier]];
+
+ // If we can't immediately find item, there are three possibilities:
+ // * It is a button in a ScrollView, or
+ // * It is contained within a popover, or
+ // * It simply does not exist.
+ // We check for each possibility here.
+ if (!self.mappedLayoutItems[[aInput nativeIdentifier]]) {
+ if ([self maybeUpdateScrollViewChild:aInput]) {
+ return true;
+ }
+ if ([self maybeUpdatePopoverChild:aInput]) {
+ return true;
+ }
+ return false;
+ }
+
+ // Update our canonical copy of the input.
+ [self replaceMappedLayoutItem:aInput];
+
+ if ([aInput baseType] == TouchBarInputBaseType::kButton) {
+ [(NSCustomTouchBarItem*)item setCustomizationLabel:[aInput title]];
+ [self updateButton:(NSCustomTouchBarItem*)item withIdentifier:[aInput nativeIdentifier]];
+ } else if ([aInput baseType] == TouchBarInputBaseType::kMainButton) {
+ [(NSCustomTouchBarItem*)item setCustomizationLabel:[aInput title]];
+ [self updateMainButton:(NSCustomTouchBarItem*)item withIdentifier:[aInput nativeIdentifier]];
+ } else if ([aInput baseType] == TouchBarInputBaseType::kScrollView) {
+ [(NSCustomTouchBarItem*)item setCustomizationLabel:[aInput title]];
+ [self updateScrollView:(NSCustomTouchBarItem*)item withIdentifier:[aInput nativeIdentifier]];
+ } else if ([aInput baseType] == TouchBarInputBaseType::kPopover) {
+ [(NSPopoverTouchBarItem*)item setCustomizationLabel:[aInput title]];
+ [self updatePopover:(NSPopoverTouchBarItem*)item withIdentifier:[aInput nativeIdentifier]];
+ for (TouchBarInput* child in [aInput children]) {
+ [(nsTouchBar*)[(NSPopoverTouchBarItem*)item popoverTouchBar] updateItem:child];
+ }
+ } else if ([aInput baseType] == TouchBarInputBaseType::kLabel) {
+ [self updateLabel:(NSTextField*)item.view withIdentifier:[aInput nativeIdentifier]];
+ }
+
+ return true;
+}
+
+- (bool)maybeUpdatePopoverChild:(TouchBarInput*)aInput {
+ for (NSTouchBarItemIdentifier identifier in self.mappedLayoutItems) {
+ TouchBarInput* potentialPopover = self.mappedLayoutItems[identifier];
+ if ([potentialPopover baseType] != TouchBarInputBaseType::kPopover) {
+ continue;
+ }
+ NSTouchBarItem* popover = [self itemForIdentifier:[potentialPopover nativeIdentifier]];
+ if (popover) {
+ if ([(nsTouchBar*)[(NSPopoverTouchBarItem*)popover popoverTouchBar] updateItem:aInput]) {
+ return true;
+ }
+ }
+ }
+ return false;
+}
+
+- (bool)maybeUpdateScrollViewChild:(TouchBarInput*)aInput {
+ NSCustomTouchBarItem* scrollViewButton = self.scrollViewButtons[[aInput nativeIdentifier]];
+ if (scrollViewButton) {
+ // ScrollView buttons are similar to mainButtons except for their width.
+ [self updateMainButton:scrollViewButton withIdentifier:[aInput nativeIdentifier]];
+ NSButton* button = (NSButton*)scrollViewButton.view;
+ uint32_t buttonSize = MAX(button.attributedTitle.size.width + kInputIconSize + kInputSpacing,
+ kScrollViewButtonWidth);
+ [[button widthAnchor] constraintGreaterThanOrEqualToConstant:buttonSize].active = YES;
+ }
+ // Updating the TouchBarInput* in the ScrollView's mChildren array.
+ for (NSTouchBarItemIdentifier identifier in self.mappedLayoutItems) {
+ TouchBarInput* potentialScrollView = self.mappedLayoutItems[identifier];
+ if ([potentialScrollView baseType] != TouchBarInputBaseType::kScrollView) {
+ continue;
+ }
+ for (uint32_t i = 0; i < [[potentialScrollView children] count]; ++i) {
+ TouchBarInput* child = [potentialScrollView children][i];
+ if (![[child nativeIdentifier] isEqualToString:[aInput nativeIdentifier]]) {
+ continue;
+ }
+ [[potentialScrollView children] replaceObjectAtIndex:i withObject:aInput];
+ [child release];
+ return true;
+ }
+ }
+ return false;
+}
+
+- (void)replaceMappedLayoutItem:(TouchBarInput*)aItem {
+ [self.mappedLayoutItems[[aItem nativeIdentifier]] release];
+ self.mappedLayoutItems[[aItem nativeIdentifier]] = aItem;
+}
+
+- (void)updateButton:(NSCustomTouchBarItem*)aButton
+ withIdentifier:(NSTouchBarItemIdentifier)aIdentifier {
+ if (!aButton || !aIdentifier) {
+ return;
+ }
+
+ TouchBarInput* input = self.mappedLayoutItems[aIdentifier];
+ if (!input) {
+ return;
+ }
+
+ NSButton* button = (NSButton*)[aButton view];
+ button.title = [input title];
+ if ([input imageURI]) {
+ [button setImagePosition:NSImageOnly];
+ [self loadIconForInput:input forItem:aButton];
+ // Because we are hiding the title, NSAccessibility also does not get it.
+ // Therefore, set an accessibility label as alternative text for image-only buttons.
+ [button setAccessibilityLabel:[input title]];
+ }
+
+ [button setEnabled:![input isDisabled]];
+ if ([input color]) {
+ button.bezelColor = [input color];
+ }
+
+ objc_setAssociatedObject(button, &sIdentifierAssociationKey, aIdentifier,
+ OBJC_ASSOCIATION_RETAIN);
+}
+
+- (void)updateMainButton:(NSCustomTouchBarItem*)aMainButton
+ withIdentifier:(NSTouchBarItemIdentifier)aIdentifier {
+ if (!aMainButton || !aIdentifier) {
+ return;
+ }
+
+ TouchBarInput* input = self.mappedLayoutItems[aIdentifier];
+ if (!input) {
+ return;
+ }
+
+ [self updateButton:aMainButton withIdentifier:aIdentifier];
+ NSButton* button = (NSButton*)[aMainButton view];
+
+ // If empty, string is still being localized. Display a blank input instead.
+ if ([[input title] isEqualToString:@""]) {
+ [button setImagePosition:NSNoImage];
+ } else {
+ [button setImagePosition:NSImageLeft];
+ }
+ button.imageHugsTitle = YES;
+ [button.widthAnchor constraintGreaterThanOrEqualToConstant:MAIN_BUTTON_WIDTH].active = YES;
+ [button setContentHuggingPriority:1.0 forOrientation:NSLayoutConstraintOrientationHorizontal];
+}
+
+- (void)updatePopover:(NSPopoverTouchBarItem*)aPopoverItem
+ withIdentifier:(NSTouchBarItemIdentifier)aIdentifier {
+ if (!aPopoverItem || !aIdentifier) {
+ return;
+ }
+
+ TouchBarInput* input = self.mappedLayoutItems[aIdentifier];
+ if (!input) {
+ return;
+ }
+
+ aPopoverItem.showsCloseButton = YES;
+ if ([input imageURI]) {
+ [self loadIconForInput:input forItem:aPopoverItem];
+ } else if ([input title]) {
+ aPopoverItem.collapsedRepresentationLabel = [input title];
+ }
+
+ // Special handling to show/hide the search popover if the Urlbar is focused.
+ if ([[input nativeIdentifier] isEqualToString:[TouchBarInput searchPopoverIdentifier]]) {
+ // We can reach this code during window shutdown. We only want to toggle
+ // showPopover if we are in a normal running state.
+ if (!mTouchBarHelper) {
+ return;
+ }
+ bool urlbarIsFocused = false;
+ mTouchBarHelper->GetIsUrlbarFocused(&urlbarIsFocused);
+ if (urlbarIsFocused) {
+ [aPopoverItem showPopover:self];
+ }
+ }
+}
+
+- (void)updateScrollView:(NSCustomTouchBarItem*)aScrollViewItem
+ withIdentifier:(NSTouchBarItemIdentifier)aIdentifier {
+ if (!aScrollViewItem || !aIdentifier) {
+ return;
+ }
+
+ TouchBarInput* input = self.mappedLayoutItems[aIdentifier];
+ if (!input || ![input children]) {
+ return;
+ }
+
+ NSMutableDictionary* constraintViews = [NSMutableDictionary dictionary];
+ NSView* documentView = [[NSView alloc] initWithFrame:NSZeroRect];
+ NSString* layoutFormat = @"H:|-8-";
+ NSSize size = NSMakeSize(kInputSpacing, 30);
+ // Layout strings allow only alphanumeric characters. We will use this
+ // NSCharacterSet to strip illegal characters.
+ NSCharacterSet* charactersToRemove = [[NSCharacterSet alphanumericCharacterSet] invertedSet];
+
+ for (TouchBarInput* childInput in [input children]) {
+ if ([childInput baseType] != TouchBarInputBaseType::kButton) {
+ continue;
+ }
+ [self replaceMappedLayoutItem:childInput];
+ NSCustomTouchBarItem* newItem =
+ [[NSCustomTouchBarItem alloc] initWithIdentifier:[childInput nativeIdentifier]];
+ NSButton* button = [NSButton buttonWithTitle:[childInput title]
+ target:self
+ action:@selector(touchBarAction:)];
+ newItem.view = button;
+ // ScrollView buttons are similar to mainButtons except for their width.
+ [self updateMainButton:newItem withIdentifier:[childInput nativeIdentifier]];
+ uint32_t buttonSize = MAX(button.attributedTitle.size.width + kInputIconSize + kInputSpacing,
+ kScrollViewButtonWidth);
+ [[button widthAnchor] constraintGreaterThanOrEqualToConstant:buttonSize].active = YES;
+
+ NSCustomTouchBarItem* tempItem = self.scrollViewButtons[[childInput nativeIdentifier]];
+ self.scrollViewButtons[[childInput nativeIdentifier]] = newItem;
+ [tempItem release];
+
+ button.translatesAutoresizingMaskIntoConstraints = NO;
+ [documentView addSubview:button];
+ NSString* layoutKey = [[[childInput nativeIdentifier]
+ componentsSeparatedByCharactersInSet:charactersToRemove] componentsJoinedByString:@""];
+
+ // Iteratively create our layout string.
+ layoutFormat =
+ [layoutFormat stringByAppendingString:[NSString stringWithFormat:@"[%@]-8-", layoutKey]];
+ [constraintViews setObject:button forKey:layoutKey];
+ size.width += kInputSpacing + buttonSize;
+ }
+ layoutFormat = [layoutFormat stringByAppendingString:[NSString stringWithFormat:@"|"]];
+ NSArray* hConstraints =
+ [NSLayoutConstraint constraintsWithVisualFormat:layoutFormat
+ options:NSLayoutFormatAlignAllCenterY
+ metrics:nil
+ views:constraintViews];
+ NSScrollView* scrollView =
+ [[NSScrollView alloc] initWithFrame:CGRectMake(0, 0, size.width, size.height)];
+ [documentView setFrame:NSMakeRect(0, 0, size.width, size.height)];
+ [NSLayoutConstraint activateConstraints:hConstraints];
+ scrollView.documentView = documentView;
+
+ aScrollViewItem.view = scrollView;
+}
+
+- (void)updateLabel:(NSTextField*)aLabel withIdentifier:(NSTouchBarItemIdentifier)aIdentifier {
+ if (!aLabel || !aIdentifier) {
+ return;
+ }
+
+ TouchBarInput* input = self.mappedLayoutItems[aIdentifier];
+ if (!input || ![input title]) {
+ return;
+ }
+ [aLabel setStringValue:[input title]];
+}
+
+- (NSTouchBarItem*)makeShareScrubberForIdentifier:(NSTouchBarItemIdentifier)aIdentifier {
+ TouchBarInput* input = self.mappedLayoutItems[aIdentifier];
+ // System-default share menu
+ NSSharingServicePickerTouchBarItem* servicesItem =
+ [[NSSharingServicePickerTouchBarItem alloc] initWithIdentifier:aIdentifier];
+
+ // buttonImage needs to be set to nil while we wait for our icon to load.
+ // Otherwise, the default Apple share icon is automatically loaded.
+ servicesItem.buttonImage = nil;
+
+ [self loadIconForInput:input forItem:servicesItem];
+
+ servicesItem.delegate = self;
+ return servicesItem;
+}
+
+- (void)showPopover:(TouchBarInput*)aPopover showing:(bool)aShowing {
+ if (!aPopover) {
+ return;
+ }
+ NSPopoverTouchBarItem* popoverItem =
+ (NSPopoverTouchBarItem*)[self itemForIdentifier:[aPopover nativeIdentifier]];
+ if (!popoverItem) {
+ return;
+ }
+ if (aShowing) {
+ [popoverItem showPopover:self];
+ } else {
+ [popoverItem dismissPopover:self];
+ }
+}
+
+- (void)touchBarAction:(id)aSender {
+ NSTouchBarItemIdentifier identifier =
+ objc_getAssociatedObject(aSender, &sIdentifierAssociationKey);
+ if (!identifier || [identifier isEqualToString:@""]) {
+ return;
+ }
+
+ TouchBarInput* input = self.mappedLayoutItems[identifier];
+ if (!input) {
+ return;
+ }
+
+ nsCOMPtr<nsITouchBarInputCallback> callback = [input callback];
+ if (!callback) {
+ NSLog(@"Touch Bar action attempted with no valid callback! Identifier: %@",
+ [input nativeIdentifier]);
+ return;
+ }
+ callback->OnCommand();
+}
+
+- (void)loadIconForInput:(TouchBarInput*)aInput forItem:(NSTouchBarItem*)aItem {
+ if (!aInput || ![aInput imageURI] || !aItem || !mTouchBarHelper) {
+ return;
+ }
+
+ RefPtr<nsTouchBarInputIcon> icon = [aInput icon];
+
+ if (!icon) {
+ RefPtr<Document> document;
+ nsresult rv = mTouchBarHelper->GetDocument(getter_AddRefs(document));
+ if (NS_FAILED(rv) || !document) {
+ return;
+ }
+ icon = new nsTouchBarInputIcon(document, aInput, aItem);
+ [aInput setIcon:icon];
+ }
+ icon->SetupIcon([aInput imageURI]);
+}
+
+- (void)releaseJSObjects {
+ mTouchBarHelper = nil;
+
+ for (NSTouchBarItemIdentifier identifier in self.mappedLayoutItems) {
+ TouchBarInput* input = self.mappedLayoutItems[identifier];
+ if (!input) {
+ continue;
+ }
+
+ // Childless popovers contain the default Touch Bar as its popoverTouchBar.
+ // We check for [input children] since the default Touch Bar contains a
+ // popover (search-popover), so this would infinitely loop if there was no check.
+ if ([input baseType] == TouchBarInputBaseType::kPopover && [input children]) {
+ NSTouchBarItem* item = [self itemForIdentifier:identifier];
+ [(nsTouchBar*)[(NSPopoverTouchBarItem*)item popoverTouchBar] releaseJSObjects];
+ }
+
+ [input releaseJSObjects];
+ }
+}
+
+#pragma mark - NSSharingServicePickerTouchBarItemDelegate
+
+- (NSArray*)itemsForSharingServicePickerTouchBarItem:
+ (NSSharingServicePickerTouchBarItem*)aPickerTouchBarItem {
+ NSURL* urlToShare = nil;
+ NSString* titleToShare = @"";
+ nsAutoString url;
+ nsAutoString title;
+ if (mTouchBarHelper) {
+ nsresult rv = mTouchBarHelper->GetActiveUrl(url);
+ if (!NS_FAILED(rv)) {
+ urlToShare = [NSURL URLWithString:nsCocoaUtils::ToNSString(url)];
+ // NSURL URLWithString returns nil if the URL is invalid. At this point,
+ // it is too late to simply shut down the share menu, so we default to
+ // about:blank if the share button is clicked when the URL is invalid.
+ if (urlToShare == nil) {
+ urlToShare = [NSURL URLWithString:@"about:blank"];
+ }
+ }
+
+ rv = mTouchBarHelper->GetActiveTitle(title);
+ if (!NS_FAILED(rv)) {
+ titleToShare = nsCocoaUtils::ToNSString(title);
+ }
+ }
+
+ return @[ urlToShare, titleToShare ];
+}
+
+- (NSArray<NSSharingService*>*)sharingServicePicker:(NSSharingServicePicker*)aSharingServicePicker
+ sharingServicesForItems:(NSArray*)aItems
+ proposedSharingServices:(NSArray<NSSharingService*>*)aProposedServices {
+ // redundant services
+ NSArray* excludedServices = @[
+ @"com.apple.share.System.add-to-safari-reading-list",
+ ];
+
+ NSArray* sharingServices = [aProposedServices
+ filteredArrayUsingPredicate:[NSPredicate
+ predicateWithFormat:@"NOT (name IN %@)", excludedServices]];
+
+ return sharingServices;
+}
+
+@end