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