Index: chrome/browser/ui/cocoa/share_menu_controller.mm |
diff --git a/chrome/browser/ui/cocoa/share_menu_controller.mm b/chrome/browser/ui/cocoa/share_menu_controller.mm |
new file mode 100644 |
index 0000000000000000000000000000000000000000..9853447fa7ef6e35a7f2781bfb089c2a139d5093 |
--- /dev/null |
+++ b/chrome/browser/ui/cocoa/share_menu_controller.mm |
@@ -0,0 +1,215 @@ |
+// Copyright 2017 The Chromium Authors. All rights reserved. |
+// Use of this source code is governed by a BSD-style license that can be |
+// found in the LICENSE file. |
+ |
+#import "chrome/browser/ui/cocoa/share_menu_controller.h" |
+ |
+#include "base/mac/foundation_util.h" |
+#include "base/mac/mac_util.h" |
+#include "base/strings/sys_string_conversions.h" |
+#include "chrome/browser/ui/browser.h" |
+#include "chrome/browser/ui/browser_commands.h" |
+#include "chrome/browser/ui/browser_window.h" |
+#import "chrome/browser/ui/cocoa/browser_window_controller.h" |
+#import "chrome/browser/ui/cocoa/fast_resize_view.h" |
+#import "chrome/browser/ui/cocoa/last_active_browser_cocoa.h" |
+#include "chrome/grit/generated_resources.h" |
+#include "net/base/mac/url_conversions.h" |
+#include "ui/base/l10n/l10n_util_mac.h" |
+#include "ui/gfx/geometry/rect.h" |
+#include "ui/gfx/image/image.h" |
+#include "ui/snapshot/snapshot.h" |
+ |
+namespace { |
+NSString* const kExtensionPrefPanePath = |
+ @"/System/Library/PreferencePanes/Extensions.prefPane"; |
+const UInt32 kOpenSharingSubpaneDescriptorType = 'ptru'; |
Robert Sesek
2017/06/28 15:37:54
Is this documented, or did you find it? Maybe leav
lgrey
2017/06/29 21:11:50
Done.
|
+NSString* const kOpenSharingSubpaneActionKey = @"action"; |
+NSString* const kOpenSharingSubpaneActionValue = @"revealExtensionPoint"; |
+NSString* const kOpenSharingSubpaneProtocolKey = @"protocol"; |
+NSString* const kOpenSharingSubpaneProtocolValue = @"com.apple.share-services"; |
+} |
Robert Sesek
2017/06/28 15:37:54
" // namespace"
lgrey
2017/06/29 21:11:50
Done.
|
+ |
+@implementation ShareMenuController { |
+ NSWindow* windowForShare_; // weak |
+ NSRect rectForShare_; |
Robert Sesek
2017/06/28 15:37:54
Document what these are used for.
lgrey
2017/06/29 21:11:50
Done.
|
+ base::scoped_nsobject<NSImage> snapshotForShare_; |
+} |
+ |
++ (BOOL)shouldShowMoreItem { |
+ return base::mac::IsAtLeastOS10_10(); |
+} |
+ |
+// NSMenuDelegate |
+- (void)menuNeedsUpdate:(NSMenu*)menu { |
+ [menu removeAllItems]; |
+ [menu setAutoenablesItems:NO]; |
+ |
+ Browser* lastActiveBrowser = chrome::GetLastActiveBrowser(); |
+ BOOL canShare = |
+ lastActiveBrowser != nullptr && |
+ // Avoid |CanEmailPageLocation| segfault in interactive UI tests |
+ lastActiveBrowser->tab_strip_model()->GetActiveWebContents() != nullptr && |
+ chrome::CanEmailPageLocation(chrome::GetLastActiveBrowser()); |
+ // Using a real URL instead of empty string to avoid system log about relative |
+ // URLs in the pasteboard. |
+ NSArray* services = [NSSharingService |
+ sharingServicesForItems:@[ [NSURL URLWithString:@"https://google.com"] ]]; |
+ for (NSSharingService* service in services) { |
+ // Don't include "Add to Reading List" |
+ if ([service isEqual:[NSSharingService |
Robert Sesek
2017/06/28 15:37:54
Hoist the argument to isEqual: to a local outside
lgrey
2017/06/29 21:11:50
Done.
|
+ sharingServiceNamed: |
+ NSSharingServiceNameAddToSafariReadingList]]) |
+ continue; |
+ NSMenuItem* item = [self menuItemForService:service]; |
+ [item setEnabled:canShare]; |
+ [menu addItem:item]; |
+ } |
+ if (![[self class] shouldShowMoreItem]) { |
+ return; |
+ } |
+ NSMenuItem* moreItem = [[NSMenuItem alloc] |
Robert Sesek
2017/06/28 15:37:54
This is currently leaked. Use scoped_nsobject<>.
lgrey
2017/06/29 21:11:50
:shamecube: done
|
+ initWithTitle:l10n_util::GetNSString(IDS_SHARING_MORE_MAC) |
+ action:@selector(openSharingPrefs:) |
+ keyEquivalent:@""]; |
+ moreItem.target = self; |
+ moreItem.image = [self moreImage]; |
+ [menu addItem:moreItem]; |
+} |
Robert Sesek
2017/06/28 15:37:54
nit: blank line after
lgrey
2017/06/29 21:11:50
Done.
|
+// NSSharingServiceDelegate |
+ |
+- (void)sharingService:(NSSharingService*)service |
+ didShareItems:(NSArray*)items { |
+ // TODO(lgrey): Add an UMA stat. |
+ [self clearTransitionData]; |
+} |
+ |
+- (void)sharingService:(NSSharingService*)service |
+ didFailToShareItems:(NSArray*)items |
+ error:(NSError*)error { |
+ // TODO(lgrey): Add an UMA stat. |
+ [self clearTransitionData]; |
+} |
+ |
+- (NSRect)sharingService:(NSSharingService*)service |
+ sourceFrameOnScreenForShareItem:(id)item { |
+ return rectForShare_; |
+} |
+ |
+- (NSWindow*)sharingService:(NSSharingService*)service |
+ sourceWindowForShareItems:(NSArray*)items |
+ sharingContentScope:(NSSharingContentScope*)scope { |
+ *scope = NSSharingContentScopeFull; |
+ return windowForShare_; |
+} |
+ |
+- (NSImage*)sharingService:(NSSharingService*)service |
+ transitionImageForShareItem:(id)item |
+ contentRect:(NSRect*)contentRect { |
+ return snapshotForShare_.get(); |
+} |
+ |
+// Private methods |
+ |
+// Saves details required by delegate methods for the transition animation. |
+- (void)saveTransitionDataFromBrowser:(Browser*)browser { |
+ windowForShare_ = browser->window()->GetNativeWindow(); |
Robert Sesek
2017/06/28 15:37:54
Is it possible for the window to be closed between
lgrey
2017/06/29 21:11:50
In practice, no (the delegate methods are called s
Robert Sesek
2017/06/30 21:09:34
OK, if it's synchronous then it should be fine.
|
+ |
+ NSView* contentsView = [[windowForShare_ windowController] tabContentArea]; |
+ NSRect rectInWindow = |
+ [[contentsView superview] convertRect:[contentsView frame] toView:nil]; |
+ rectForShare_ = [windowForShare_ convertRectToScreen:rectInWindow]; |
+ |
+ gfx::Image image; |
+ gfx::Rect rect = gfx::Rect(NSRectToCGRect([contentsView bounds])); |
+ if (ui::GrabViewSnapshot(contentsView, rect, &image)) { |
+ snapshotForShare_.reset([image.ToNSImage() retain]); |
Robert Sesek
2017/06/28 15:37:54
.CopyNSImage() will return a strong reference.
Yo
lgrey
2017/06/29 21:11:50
Done.
Decided against storing the gfx::Image sinc
Robert Sesek
2017/06/30 21:09:34
image_ = gfx::Image() would work, but what you're
|
+ } |
+} |
+ |
+- (void)clearTransitionData { |
+ windowForShare_ = nil; |
+ rectForShare_ = NSZeroRect; |
+ snapshotForShare_.reset(); |
+} |
+ |
+// Performs the share action using the sharing service represented by |sender|. |
+- (void)performShare:(NSMenuItem*)sender { |
+ Browser* browser = chrome::GetLastActiveBrowser(); |
+ DCHECK(browser); |
+ [self saveTransitionDataFromBrowser:browser]; |
+ |
+ content::WebContents* contents = |
+ browser->tab_strip_model()->GetActiveWebContents(); |
+ NSURL* url = net::NSURLWithGURL(contents->GetLastCommittedURL()); |
+ NSString* title = base::SysUTF16ToNSString(contents->GetTitle()); |
+ |
+ NSSharingService* service = |
+ base::mac::ObjCCastStrict<NSSharingService>([sender representedObject]); |
+ [service setDelegate:self]; |
+ [service setSubject:title]; |
+ |
+ NSArray* itemsToShare; |
+ if ([service |
+ isEqual:[NSSharingService |
+ sharingServiceNamed:NSSharingServiceNamePostOnTwitter]]) { |
+ itemsToShare = @[ url, title ]; |
+ } else { |
+ itemsToShare = @[ url ]; |
+ } |
+ [service performWithItems:itemsToShare]; |
+} |
+ |
+// Opens the "Sharing" subpane of the "Extensions" macOS preference pane. |
+- (void)openSharingPrefs:(NSMenuItem*)sender { |
+ DCHECK([[self class] shouldShowMoreItem]); |
+ NSURL* prefPaneURL = |
+ [NSURL fileURLWithPath:kExtensionPrefPanePath isDirectory:YES]; |
+ NSDictionary* args = @{ |
+ kOpenSharingSubpaneActionKey : kOpenSharingSubpaneActionValue, |
+ kOpenSharingSubpaneProtocolKey : kOpenSharingSubpaneProtocolValue |
+ }; |
+ NSData* data = [NSPropertyListSerialization |
+ dataWithPropertyList:args |
+ format:NSPropertyListXMLFormat_v1_0 |
+ options:0 |
+ error:nil]; |
+ NSAppleEventDescriptor* descriptor = [[[NSAppleEventDescriptor alloc] |
+ initWithDescriptorType:kOpenSharingSubpaneDescriptorType |
+ data:data] autorelease]; |
Robert Sesek
2017/06/28 15:37:54
Prefer scoped_nsobject to autorelease for locally
lgrey
2017/06/29 21:11:50
Done. I kind of did autorelease for aesthetic reas
Robert Sesek
2017/06/30 21:09:34
Per other comment, the .get() shouldn't be necessa
|
+ [[NSWorkspace sharedWorkspace] openURLs:@[ prefPaneURL ] |
+ withAppBundleIdentifier:nil |
+ options:NSWorkspaceLaunchAsync |
+ additionalEventParamDescriptor:descriptor |
+ launchIdentifiers:NULL]; |
+} |
+ |
+// Returns the image to be used for the "More..." menu item, or nil on macOS |
+// version where this private method is unsupported. |
+- (NSImage*)moreImage { |
+ if ([NSSharingServicePicker |
+ respondsToSelector:@selector(sharedMoreMenuImage)]) { |
+ return |
+ [NSSharingServicePicker performSelector:@selector(sharedMoreMenuImage)]; |
+ } |
+ return nil; |
+} |
+ |
+// Creates a menu item that calls |service| when invoked. |
+- (NSMenuItem*)menuItemForService:(NSSharingService*)service { |
+ BOOL isMail = [service |
+ isEqual:[NSSharingService |
+ sharingServiceNamed:NSSharingServiceNameComposeEmail]]; |
+ NSString* keyEquivalent = isMail ? @"I" : @""; |
Robert Sesek
2017/06/28 15:37:55
Look this up using AcceleratorsCocoa::GetAccelerat
lgrey
2017/06/29 21:11:50
Done.
|
+ NSString* title = isMail ? l10n_util::GetNSString(IDS_EMAIL_PAGE_LOCATION_MAC) |
+ : service.menuItemTitle; |
+ NSMenuItem* item = [[NSMenuItem alloc] initWithTitle:title |
Robert Sesek
2017/06/28 15:37:54
This could also be put in a scoped_nsobject<> and
lgrey
2017/06/29 21:11:50
Done.
|
+ action:@selector(performShare:) |
+ keyEquivalent:keyEquivalent]; |
+ [item setTarget:self]; |
+ [item setImage:[service image]]; |
+ [item setRepresentedObject:service]; |
+ return [item autorelease]; |
+} |
+ |
+@end |