Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(4118)

Unified Diff: chrome/browser/ui/cocoa/share_menu_controller.mm

Issue 2950403002: [Mac] Add system share menu
Patch Set: Guard against empty web contents Created 3 years, 6 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
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

Powered by Google App Engine
This is Rietveld 408576698