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