Bug 1880227 - Migrate Focus docs into Sphinx. r=owlish,geckoview-reviewers,android...
[gecko.git] / widget / cocoa / nsTouchBar.mm
blob067ce031298619a0f3754ae276ccfa10b3f2b949
1 /* This Source Code Form is subject to the terms of the Mozilla Public
2  * License, v. 2.0. If a copy of the MPL was not distributed with this
3  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
5 #include "nsTouchBar.h"
7 #include <objc/runtime.h>
9 #include "mozilla/MacStringHelpers.h"
10 #include "mozilla/dom/Document.h"
11 #include "nsArrayUtils.h"
12 #include "nsCocoaUtils.h"
13 #include "nsDirectoryServiceDefs.h"
14 #include "nsIArray.h"
15 #include "nsTouchBarInputIcon.h"
16 #include "nsWidgetsCID.h"
18 @implementation nsTouchBar
20 // Used to tie action strings to buttons.
21 static char sIdentifierAssociationKey;
23 // The default space between inputs, used where layout is not automatic.
24 static const uint32_t kInputSpacing = 8;
25 // The width of buttons in Apple's Share ScrollView. We use this in our
26 // ScrollViews to give them a native appearance.
27 static const uint32_t kScrollViewButtonWidth = 144;
28 static const uint32_t kInputIconSize = 16;
30 // The system default width for Touch Bar inputs is 128px. This is double.
31 #define MAIN_BUTTON_WIDTH 256
33 #pragma mark - NSTouchBarDelegate
35 - (instancetype)init {
36   return [self initWithInputs:nil];
39 - (instancetype)initWithInputs:(NSMutableArray<TouchBarInput*>*)aInputs {
40   if ((self = [super init])) {
41     mTouchBarHelper = do_GetService(NS_TOUCHBARHELPER_CID);
42     if (!mTouchBarHelper) {
43       NS_ERROR("Unable to create Touch Bar Helper.");
44       return nil;
45     }
47     self.delegate = self;
48     self.mappedLayoutItems = [NSMutableDictionary dictionary];
49     self.customizationAllowedItemIdentifiers = @[];
51     if (!aInputs) {
52       // This customization identifier is how users' custom layouts are saved by
53       // macOS. If this changes, all users' layouts would be reset to the
54       // default layout.
55       self.customizationIdentifier = [kTouchBarBaseIdentifier
56           stringByAppendingPathExtension:@"defaultbar"];
57       nsCOMPtr<nsIArray> allItems;
59       nsresult rv = mTouchBarHelper->GetAllItems(getter_AddRefs(allItems));
60       if (NS_FAILED(rv) || !allItems) {
61         return nil;
62       }
64       uint32_t itemCount = 0;
65       allItems->GetLength(&itemCount);
66       // This is copied to self.customizationAllowedItemIdentifiers.
67       // Required since [self.mappedItems allKeys] does not preserve order.
68       // One slot is added for the spacer item.
69       NSMutableArray* orderedIdentifiers =
70           [NSMutableArray arrayWithCapacity:itemCount + 1];
71       for (uint32_t i = 0; i < itemCount; ++i) {
72         nsCOMPtr<nsITouchBarInput> input = do_QueryElementAt(allItems, i);
73         if (!input) {
74           continue;
75         }
77         TouchBarInput* convertedInput;
78         NSTouchBarItemIdentifier newInputIdentifier =
79             [TouchBarInput nativeIdentifierWithXPCOM:input];
80         if (!newInputIdentifier) {
81           continue;
82         }
84         // If there is already an input in mappedLayoutItems with this
85         // identifier, that means updateItem fired before this initialization.
86         // The input cached by updateItem is more current, so we should use that
87         // one.
88         if (self.mappedLayoutItems[newInputIdentifier]) {
89           convertedInput = self.mappedLayoutItems[newInputIdentifier];
90         } else {
91           convertedInput = [[TouchBarInput alloc] initWithXPCOM:input];
92           // Add new input to dictionary for lookup of properties in delegate.
93           self.mappedLayoutItems[[convertedInput nativeIdentifier]] =
94               convertedInput;
95         }
97         orderedIdentifiers[i] = [convertedInput nativeIdentifier];
98       }
99       [orderedIdentifiers addObject:@"NSTouchBarItemIdentifierFlexibleSpace"];
100       self.customizationAllowedItemIdentifiers = [orderedIdentifiers copy];
102       NSArray* defaultItemIdentifiers = @[
103         [TouchBarInput nativeIdentifierWithType:@"button" withKey:@"back"],
104         [TouchBarInput nativeIdentifierWithType:@"button" withKey:@"forward"],
105         [TouchBarInput nativeIdentifierWithType:@"button" withKey:@"reload"],
106         [TouchBarInput nativeIdentifierWithType:@"mainButton"
107                                         withKey:@"open-location"],
108         [TouchBarInput nativeIdentifierWithType:@"button" withKey:@"new-tab"],
109         [TouchBarInput shareScrubberIdentifier],
110         [TouchBarInput searchPopoverIdentifier]
111       ];
112       self.defaultItemIdentifiers = [defaultItemIdentifiers copy];
113     } else {
114       NSMutableArray* defaultItemIdentifiers =
115           [NSMutableArray arrayWithCapacity:[aInputs count]];
116       for (TouchBarInput* input in aInputs) {
117         self.mappedLayoutItems[[input nativeIdentifier]] = input;
118         [defaultItemIdentifiers addObject:[input nativeIdentifier]];
119       }
120       self.defaultItemIdentifiers = [defaultItemIdentifiers copy];
121     }
122   }
124   return self;
127 - (void)dealloc {
128   for (NSTouchBarItemIdentifier identifier in self.mappedLayoutItems) {
129     NSTouchBarItem* item = [self itemForIdentifier:identifier];
130     if (!item) {
131       continue;
132     }
133     if ([item isKindOfClass:[NSPopoverTouchBarItem class]]) {
134       [(NSPopoverTouchBarItem*)item setCollapsedRepresentationImage:nil];
135       [(nsTouchBar*)[(NSPopoverTouchBarItem*)item popoverTouchBar] release];
136     } else if ([[item view] isKindOfClass:[NSScrollView class]]) {
137       [[(NSScrollView*)[item view] documentView] release];
138       [(NSScrollView*)[item view] release];
139     }
141     [item release];
142   }
144   [self.defaultItemIdentifiers release];
145   [self.customizationAllowedItemIdentifiers release];
146   [self.scrollViewButtons removeAllObjects];
147   [self.scrollViewButtons release];
148   [self.mappedLayoutItems removeAllObjects];
149   [self.mappedLayoutItems release];
150   [super dealloc];
153 - (NSTouchBarItem*)touchBar:(NSTouchBar*)aTouchBar
154       makeItemForIdentifier:(NSTouchBarItemIdentifier)aIdentifier {
155   if (!mTouchBarHelper) {
156     return nil;
157   }
159   TouchBarInput* input = self.mappedLayoutItems[aIdentifier];
160   if (!input) {
161     return nil;
162   }
164   if ([input baseType] == TouchBarInputBaseType::kScrubber) {
165     // We check the identifier rather than the baseType here as a special case.
166     if (![aIdentifier
167             isEqualToString:[TouchBarInput shareScrubberIdentifier]]) {
168       // We're only supporting the Share scrubber for now.
169       return nil;
170     }
171     return [self makeShareScrubberForIdentifier:aIdentifier];
172   }
174   if ([input baseType] == TouchBarInputBaseType::kPopover) {
175     NSPopoverTouchBarItem* newPopoverItem =
176         [[NSPopoverTouchBarItem alloc] initWithIdentifier:aIdentifier];
177     [newPopoverItem setCustomizationLabel:[input title]];
178     // We initialize popoverTouchBar here because we only allow setting this
179     // property on popover creation. Updating popoverTouchBar for every update
180     // of the popover item would be very expensive.
181     newPopoverItem.popoverTouchBar =
182         [[nsTouchBar alloc] initWithInputs:[input children]];
183     [self updatePopover:newPopoverItem withIdentifier:[input nativeIdentifier]];
184     return newPopoverItem;
185   }
187   // Our new item, which will be initialized depending on aIdentifier.
188   NSCustomTouchBarItem* newItem =
189       [[NSCustomTouchBarItem alloc] initWithIdentifier:aIdentifier];
190   [newItem setCustomizationLabel:[input title]];
192   if ([input baseType] == TouchBarInputBaseType::kScrollView) {
193     [self updateScrollView:newItem withIdentifier:[input nativeIdentifier]];
194     return newItem;
195   } else if ([input baseType] == TouchBarInputBaseType::kLabel) {
196     NSTextField* label = [NSTextField labelWithString:@""];
197     [self updateLabel:label withIdentifier:[input nativeIdentifier]];
198     newItem.view = label;
199     return newItem;
200   }
202   // The cases of a button or main button require the same setup.
203   NSButton* button = [NSButton buttonWithTitle:@""
204                                         target:self
205                                         action:@selector(touchBarAction:)];
206   newItem.view = button;
208   if ([input baseType] == TouchBarInputBaseType::kButton &&
209       ![[input type] hasPrefix:@"scrollView"]) {
210     [self updateButton:newItem withIdentifier:[input nativeIdentifier]];
211   } else if ([input baseType] == TouchBarInputBaseType::kMainButton) {
212     [self updateMainButton:newItem withIdentifier:[input nativeIdentifier]];
213   }
214   return newItem;
217 - (bool)updateItem:(TouchBarInput*)aInput {
218   if (!mTouchBarHelper) {
219     return false;
220   }
222   NSTouchBarItem* item = [self itemForIdentifier:[aInput nativeIdentifier]];
224   // If we can't immediately find item, there are three possibilities:
225   //   * It is a button in a ScrollView, or
226   //   * It is contained within a popover, or
227   //   * It simply does not exist.
228   // We check for each possibility here.
229   if (!self.mappedLayoutItems[[aInput nativeIdentifier]]) {
230     if ([self maybeUpdateScrollViewChild:aInput]) {
231       return true;
232     }
233     if ([self maybeUpdatePopoverChild:aInput]) {
234       return true;
235     }
236     return false;
237   }
239   // Update our canonical copy of the input.
240   [self replaceMappedLayoutItem:aInput];
242   if ([aInput baseType] == TouchBarInputBaseType::kButton) {
243     [(NSCustomTouchBarItem*)item setCustomizationLabel:[aInput title]];
244     [self updateButton:(NSCustomTouchBarItem*)item
245         withIdentifier:[aInput nativeIdentifier]];
246   } else if ([aInput baseType] == TouchBarInputBaseType::kMainButton) {
247     [(NSCustomTouchBarItem*)item setCustomizationLabel:[aInput title]];
248     [self updateMainButton:(NSCustomTouchBarItem*)item
249             withIdentifier:[aInput nativeIdentifier]];
250   } else if ([aInput baseType] == TouchBarInputBaseType::kScrollView) {
251     [(NSCustomTouchBarItem*)item setCustomizationLabel:[aInput title]];
252     [self updateScrollView:(NSCustomTouchBarItem*)item
253             withIdentifier:[aInput nativeIdentifier]];
254   } else if ([aInput baseType] == TouchBarInputBaseType::kPopover) {
255     [(NSPopoverTouchBarItem*)item setCustomizationLabel:[aInput title]];
256     [self updatePopover:(NSPopoverTouchBarItem*)item
257          withIdentifier:[aInput nativeIdentifier]];
258     for (TouchBarInput* child in [aInput children]) {
259       [(nsTouchBar*)[(NSPopoverTouchBarItem*)item popoverTouchBar]
260           updateItem:child];
261     }
262   } else if ([aInput baseType] == TouchBarInputBaseType::kLabel) {
263     [self updateLabel:(NSTextField*)item.view
264         withIdentifier:[aInput nativeIdentifier]];
265   }
267   return true;
270 - (bool)maybeUpdatePopoverChild:(TouchBarInput*)aInput {
271   for (NSTouchBarItemIdentifier identifier in self.mappedLayoutItems) {
272     TouchBarInput* potentialPopover = self.mappedLayoutItems[identifier];
273     if ([potentialPopover baseType] != TouchBarInputBaseType::kPopover) {
274       continue;
275     }
276     NSTouchBarItem* popover =
277         [self itemForIdentifier:[potentialPopover nativeIdentifier]];
278     if (popover) {
279       if ([(nsTouchBar*)[(NSPopoverTouchBarItem*)popover popoverTouchBar]
280               updateItem:aInput]) {
281         return true;
282       }
283     }
284   }
285   return false;
288 - (bool)maybeUpdateScrollViewChild:(TouchBarInput*)aInput {
289   NSCustomTouchBarItem* scrollViewButton =
290       self.scrollViewButtons[[aInput nativeIdentifier]];
291   if (scrollViewButton) {
292     // ScrollView buttons are similar to mainButtons except for their width.
293     [self updateMainButton:scrollViewButton
294             withIdentifier:[aInput nativeIdentifier]];
295     NSButton* button = (NSButton*)scrollViewButton.view;
296     uint32_t buttonSize =
297         MAX(button.attributedTitle.size.width + kInputIconSize + kInputSpacing,
298             kScrollViewButtonWidth);
299     [[button widthAnchor] constraintGreaterThanOrEqualToConstant:buttonSize]
300         .active = YES;
301   }
302   // Updating the TouchBarInput* in the ScrollView's mChildren array.
303   for (NSTouchBarItemIdentifier identifier in self.mappedLayoutItems) {
304     TouchBarInput* potentialScrollView = self.mappedLayoutItems[identifier];
305     if ([potentialScrollView baseType] != TouchBarInputBaseType::kScrollView) {
306       continue;
307     }
308     for (uint32_t i = 0; i < [[potentialScrollView children] count]; ++i) {
309       TouchBarInput* child = [potentialScrollView children][i];
310       if (![[child nativeIdentifier]
311               isEqualToString:[aInput nativeIdentifier]]) {
312         continue;
313       }
314       [[potentialScrollView children] replaceObjectAtIndex:i withObject:aInput];
315       [child release];
316       return true;
317     }
318   }
319   return false;
322 - (void)replaceMappedLayoutItem:(TouchBarInput*)aItem {
323   [self.mappedLayoutItems[[aItem nativeIdentifier]] release];
324   self.mappedLayoutItems[[aItem nativeIdentifier]] = aItem;
327 - (void)updateButton:(NSCustomTouchBarItem*)aButton
328       withIdentifier:(NSTouchBarItemIdentifier)aIdentifier {
329   if (!aButton || !aIdentifier) {
330     return;
331   }
333   TouchBarInput* input = self.mappedLayoutItems[aIdentifier];
334   if (!input) {
335     return;
336   }
338   NSButton* button = (NSButton*)[aButton view];
339   button.title = [input title];
340   if ([input imageURI]) {
341     [button setImagePosition:NSImageOnly];
342     [self loadIconForInput:input forItem:aButton];
343     // Because we are hiding the title, NSAccessibility also does not get it.
344     // Therefore, set an accessibility label as alternative text for image-only
345     // buttons.
346     [button setAccessibilityLabel:[input title]];
347   }
349   [button setEnabled:![input isDisabled]];
350   if ([input color]) {
351     button.bezelColor = [input color];
352   }
354   objc_setAssociatedObject(button, &sIdentifierAssociationKey, aIdentifier,
355                            OBJC_ASSOCIATION_RETAIN);
358 - (void)updateMainButton:(NSCustomTouchBarItem*)aMainButton
359           withIdentifier:(NSTouchBarItemIdentifier)aIdentifier {
360   if (!aMainButton || !aIdentifier) {
361     return;
362   }
364   TouchBarInput* input = self.mappedLayoutItems[aIdentifier];
365   if (!input) {
366     return;
367   }
369   [self updateButton:aMainButton withIdentifier:aIdentifier];
370   NSButton* button = (NSButton*)[aMainButton view];
372   // If empty, string is still being localized. Display a blank input instead.
373   if ([[input title] isEqualToString:@""]) {
374     [button setImagePosition:NSNoImage];
375   } else {
376     [button setImagePosition:NSImageLeft];
377   }
378   button.imageHugsTitle = YES;
379   [button.widthAnchor constraintGreaterThanOrEqualToConstant:MAIN_BUTTON_WIDTH]
380       .active = YES;
381   [button setContentHuggingPriority:1.0
382                      forOrientation:NSLayoutConstraintOrientationHorizontal];
385 - (void)updatePopover:(NSPopoverTouchBarItem*)aPopoverItem
386        withIdentifier:(NSTouchBarItemIdentifier)aIdentifier {
387   if (!aPopoverItem || !aIdentifier) {
388     return;
389   }
391   TouchBarInput* input = self.mappedLayoutItems[aIdentifier];
392   if (!input) {
393     return;
394   }
396   aPopoverItem.showsCloseButton = YES;
397   if ([input imageURI]) {
398     [self loadIconForInput:input forItem:aPopoverItem];
399   } else if ([input title]) {
400     aPopoverItem.collapsedRepresentationLabel = [input title];
401   }
403   // Special handling to show/hide the search popover if the Urlbar is focused.
404   if ([[input nativeIdentifier]
405           isEqualToString:[TouchBarInput searchPopoverIdentifier]]) {
406     // We can reach this code during window shutdown. We only want to toggle
407     // showPopover if we are in a normal running state.
408     if (!mTouchBarHelper) {
409       return;
410     }
411     bool urlbarIsFocused = false;
412     mTouchBarHelper->GetIsUrlbarFocused(&urlbarIsFocused);
413     if (urlbarIsFocused) {
414       [aPopoverItem showPopover:self];
415     }
416   }
419 - (void)updateScrollView:(NSCustomTouchBarItem*)aScrollViewItem
420           withIdentifier:(NSTouchBarItemIdentifier)aIdentifier {
421   if (!aScrollViewItem || !aIdentifier) {
422     return;
423   }
425   TouchBarInput* input = self.mappedLayoutItems[aIdentifier];
426   if (!input || ![input children]) {
427     return;
428   }
430   NSMutableDictionary* constraintViews = [NSMutableDictionary dictionary];
431   NSView* documentView = [[NSView alloc] initWithFrame:NSZeroRect];
432   NSString* layoutFormat = @"H:|-8-";
433   NSSize size = NSMakeSize(kInputSpacing, 30);
434   // Layout strings allow only alphanumeric characters. We will use this
435   // NSCharacterSet to strip illegal characters.
436   NSCharacterSet* charactersToRemove =
437       [[NSCharacterSet alphanumericCharacterSet] invertedSet];
439   for (TouchBarInput* childInput in [input children]) {
440     if ([childInput baseType] != TouchBarInputBaseType::kButton) {
441       continue;
442     }
443     [self replaceMappedLayoutItem:childInput];
444     NSCustomTouchBarItem* newItem = [[NSCustomTouchBarItem alloc]
445         initWithIdentifier:[childInput nativeIdentifier]];
446     NSButton* button = [NSButton buttonWithTitle:[childInput title]
447                                           target:self
448                                           action:@selector(touchBarAction:)];
449     newItem.view = button;
450     // ScrollView buttons are similar to mainButtons except for their width.
451     [self updateMainButton:newItem
452             withIdentifier:[childInput nativeIdentifier]];
453     uint32_t buttonSize =
454         MAX(button.attributedTitle.size.width + kInputIconSize + kInputSpacing,
455             kScrollViewButtonWidth);
456     [[button widthAnchor] constraintGreaterThanOrEqualToConstant:buttonSize]
457         .active = YES;
459     NSCustomTouchBarItem* tempItem =
460         self.scrollViewButtons[[childInput nativeIdentifier]];
461     self.scrollViewButtons[[childInput nativeIdentifier]] = newItem;
462     [tempItem release];
464     button.translatesAutoresizingMaskIntoConstraints = NO;
465     [documentView addSubview:button];
466     NSString* layoutKey = [[[childInput nativeIdentifier]
467         componentsSeparatedByCharactersInSet:charactersToRemove]
468         componentsJoinedByString:@""];
470     // Iteratively create our layout string.
471     layoutFormat = [layoutFormat
472         stringByAppendingString:[NSString
473                                     stringWithFormat:@"[%@]-8-", layoutKey]];
474     [constraintViews setObject:button forKey:layoutKey];
475     size.width += kInputSpacing + buttonSize;
476   }
477   layoutFormat =
478       [layoutFormat stringByAppendingString:[NSString stringWithFormat:@"|"]];
479   NSArray* hConstraints = [NSLayoutConstraint
480       constraintsWithVisualFormat:layoutFormat
481                           options:NSLayoutFormatAlignAllCenterY
482                           metrics:nil
483                             views:constraintViews];
484   NSScrollView* scrollView = [[NSScrollView alloc]
485       initWithFrame:CGRectMake(0, 0, size.width, size.height)];
486   [documentView setFrame:NSMakeRect(0, 0, size.width, size.height)];
487   [NSLayoutConstraint activateConstraints:hConstraints];
488   scrollView.documentView = documentView;
490   aScrollViewItem.view = scrollView;
493 - (void)updateLabel:(NSTextField*)aLabel
494      withIdentifier:(NSTouchBarItemIdentifier)aIdentifier {
495   if (!aLabel || !aIdentifier) {
496     return;
497   }
499   TouchBarInput* input = self.mappedLayoutItems[aIdentifier];
500   if (!input || ![input title]) {
501     return;
502   }
503   [aLabel setStringValue:[input title]];
506 - (NSTouchBarItem*)makeShareScrubberForIdentifier:
507     (NSTouchBarItemIdentifier)aIdentifier {
508   TouchBarInput* input = self.mappedLayoutItems[aIdentifier];
509   // System-default share menu
510   NSSharingServicePickerTouchBarItem* servicesItem =
511       [[NSSharingServicePickerTouchBarItem alloc]
512           initWithIdentifier:aIdentifier];
514   // buttonImage needs to be set to nil while we wait for our icon to load.
515   // Otherwise, the default Apple share icon is automatically loaded.
516   servicesItem.buttonImage = nil;
518   [self loadIconForInput:input forItem:servicesItem];
520   servicesItem.delegate = self;
521   return servicesItem;
524 - (void)showPopover:(TouchBarInput*)aPopover showing:(bool)aShowing {
525   if (!aPopover) {
526     return;
527   }
528   NSPopoverTouchBarItem* popoverItem = (NSPopoverTouchBarItem*)[self
529       itemForIdentifier:[aPopover nativeIdentifier]];
530   if (!popoverItem) {
531     return;
532   }
533   if (aShowing) {
534     [popoverItem showPopover:self];
535   } else {
536     [popoverItem dismissPopover:self];
537   }
540 - (void)touchBarAction:(id)aSender {
541   NSTouchBarItemIdentifier identifier =
542       objc_getAssociatedObject(aSender, &sIdentifierAssociationKey);
543   if (!identifier || [identifier isEqualToString:@""]) {
544     return;
545   }
547   TouchBarInput* input = self.mappedLayoutItems[identifier];
548   if (!input) {
549     return;
550   }
552   nsCOMPtr<nsITouchBarInputCallback> callback = [input callback];
553   if (!callback) {
554     NSLog(@"Touch Bar action attempted with no valid callback! Identifier: %@",
555           [input nativeIdentifier]);
556     return;
557   }
558   callback->OnCommand();
561 - (void)loadIconForInput:(TouchBarInput*)aInput forItem:(NSTouchBarItem*)aItem {
562   if (!aInput || ![aInput imageURI] || !aItem || !mTouchBarHelper) {
563     return;
564   }
566   RefPtr<nsTouchBarInputIcon> icon = [aInput icon];
568   if (!icon) {
569     RefPtr<Document> document;
570     nsresult rv = mTouchBarHelper->GetDocument(getter_AddRefs(document));
571     if (NS_FAILED(rv) || !document) {
572       return;
573     }
574     icon = new nsTouchBarInputIcon(document, aInput, aItem);
575     [aInput setIcon:icon];
576   }
577   icon->SetupIcon([aInput imageURI]);
580 - (void)releaseJSObjects {
581   mTouchBarHelper = nil;
583   for (NSTouchBarItemIdentifier identifier in self.mappedLayoutItems) {
584     TouchBarInput* input = self.mappedLayoutItems[identifier];
585     if (!input) {
586       continue;
587     }
589     // Childless popovers contain the default Touch Bar as its popoverTouchBar.
590     // We check for [input children] since the default Touch Bar contains a
591     // popover (search-popover), so this would infinitely loop if there was no
592     // check.
593     if ([input baseType] == TouchBarInputBaseType::kPopover &&
594         [input children]) {
595       NSTouchBarItem* item = [self itemForIdentifier:identifier];
596       [(nsTouchBar*)[(NSPopoverTouchBarItem*)item popoverTouchBar]
597           releaseJSObjects];
598     }
600     [input releaseJSObjects];
601   }
604 #pragma mark - NSSharingServicePickerTouchBarItemDelegate
606 - (NSArray*)itemsForSharingServicePickerTouchBarItem:
607     (NSSharingServicePickerTouchBarItem*)aPickerTouchBarItem {
608   NSURL* urlToShare = nil;
609   NSString* titleToShare = @"";
610   nsAutoString url;
611   nsAutoString title;
612   if (mTouchBarHelper) {
613     nsresult rv = mTouchBarHelper->GetActiveUrl(url);
614     if (!NS_FAILED(rv)) {
615       urlToShare = [NSURL URLWithString:nsCocoaUtils::ToNSString(url)];
616       // NSURL URLWithString returns nil if the URL is invalid. At this point,
617       // it is too late to simply shut down the share menu, so we default to
618       // about:blank if the share button is clicked when the URL is invalid.
619       if (urlToShare == nil) {
620         urlToShare = [NSURL URLWithString:@"about:blank"];
621       }
622     }
624     rv = mTouchBarHelper->GetActiveTitle(title);
625     if (!NS_FAILED(rv)) {
626       titleToShare = nsCocoaUtils::ToNSString(title);
627     }
628   }
630   return @[ urlToShare, titleToShare ];
633 - (NSArray<NSSharingService*>*)
634        sharingServicePicker:(NSSharingServicePicker*)aSharingServicePicker
635     sharingServicesForItems:(NSArray*)aItems
636     proposedSharingServices:(NSArray<NSSharingService*>*)aProposedServices {
637   // redundant services
638   NSArray* excludedServices = @[
639     @"com.apple.share.System.add-to-safari-reading-list",
640   ];
642   NSArray* sharingServices = [aProposedServices
643       filteredArrayUsingPredicate:[NSPredicate
644                                       predicateWithFormat:@"NOT (name IN %@)",
645                                                           excludedServices]];
647   return sharingServices;
650 @end