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

Side by Side 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, 5 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 unified diff | Download patch
OLDNEW
(Empty)
1 // Copyright 2017 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #import "chrome/browser/ui/cocoa/share_menu_controller.h"
6
7 #include "base/mac/foundation_util.h"
8 #include "base/mac/mac_util.h"
9 #include "base/strings/sys_string_conversions.h"
10 #include "chrome/browser/ui/browser.h"
11 #include "chrome/browser/ui/browser_commands.h"
12 #include "chrome/browser/ui/browser_window.h"
13 #import "chrome/browser/ui/cocoa/browser_window_controller.h"
14 #import "chrome/browser/ui/cocoa/fast_resize_view.h"
15 #import "chrome/browser/ui/cocoa/last_active_browser_cocoa.h"
16 #include "chrome/grit/generated_resources.h"
17 #include "net/base/mac/url_conversions.h"
18 #include "ui/base/l10n/l10n_util_mac.h"
19 #include "ui/gfx/geometry/rect.h"
20 #include "ui/gfx/image/image.h"
21 #include "ui/snapshot/snapshot.h"
22
23 namespace {
24 NSString* const kExtensionPrefPanePath =
25 @"/System/Library/PreferencePanes/Extensions.prefPane";
26 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.
27 NSString* const kOpenSharingSubpaneActionKey = @"action";
28 NSString* const kOpenSharingSubpaneActionValue = @"revealExtensionPoint";
29 NSString* const kOpenSharingSubpaneProtocolKey = @"protocol";
30 NSString* const kOpenSharingSubpaneProtocolValue = @"com.apple.share-services";
31 }
Robert Sesek 2017/06/28 15:37:54 " // namespace"
lgrey 2017/06/29 21:11:50 Done.
32
33 @implementation ShareMenuController {
34 NSWindow* windowForShare_; // weak
35 NSRect rectForShare_;
Robert Sesek 2017/06/28 15:37:54 Document what these are used for.
lgrey 2017/06/29 21:11:50 Done.
36 base::scoped_nsobject<NSImage> snapshotForShare_;
37 }
38
39 + (BOOL)shouldShowMoreItem {
40 return base::mac::IsAtLeastOS10_10();
41 }
42
43 // NSMenuDelegate
44 - (void)menuNeedsUpdate:(NSMenu*)menu {
45 [menu removeAllItems];
46 [menu setAutoenablesItems:NO];
47
48 Browser* lastActiveBrowser = chrome::GetLastActiveBrowser();
49 BOOL canShare =
50 lastActiveBrowser != nullptr &&
51 // Avoid |CanEmailPageLocation| segfault in interactive UI tests
52 lastActiveBrowser->tab_strip_model()->GetActiveWebContents() != nullptr &&
53 chrome::CanEmailPageLocation(chrome::GetLastActiveBrowser());
54 // Using a real URL instead of empty string to avoid system log about relative
55 // URLs in the pasteboard.
56 NSArray* services = [NSSharingService
57 sharingServicesForItems:@[ [NSURL URLWithString:@"https://google.com"] ]];
58 for (NSSharingService* service in services) {
59 // Don't include "Add to Reading List"
60 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.
61 sharingServiceNamed:
62 NSSharingServiceNameAddToSafariReadingList]])
63 continue;
64 NSMenuItem* item = [self menuItemForService:service];
65 [item setEnabled:canShare];
66 [menu addItem:item];
67 }
68 if (![[self class] shouldShowMoreItem]) {
69 return;
70 }
71 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
72 initWithTitle:l10n_util::GetNSString(IDS_SHARING_MORE_MAC)
73 action:@selector(openSharingPrefs:)
74 keyEquivalent:@""];
75 moreItem.target = self;
76 moreItem.image = [self moreImage];
77 [menu addItem:moreItem];
78 }
Robert Sesek 2017/06/28 15:37:54 nit: blank line after
lgrey 2017/06/29 21:11:50 Done.
79 // NSSharingServiceDelegate
80
81 - (void)sharingService:(NSSharingService*)service
82 didShareItems:(NSArray*)items {
83 // TODO(lgrey): Add an UMA stat.
84 [self clearTransitionData];
85 }
86
87 - (void)sharingService:(NSSharingService*)service
88 didFailToShareItems:(NSArray*)items
89 error:(NSError*)error {
90 // TODO(lgrey): Add an UMA stat.
91 [self clearTransitionData];
92 }
93
94 - (NSRect)sharingService:(NSSharingService*)service
95 sourceFrameOnScreenForShareItem:(id)item {
96 return rectForShare_;
97 }
98
99 - (NSWindow*)sharingService:(NSSharingService*)service
100 sourceWindowForShareItems:(NSArray*)items
101 sharingContentScope:(NSSharingContentScope*)scope {
102 *scope = NSSharingContentScopeFull;
103 return windowForShare_;
104 }
105
106 - (NSImage*)sharingService:(NSSharingService*)service
107 transitionImageForShareItem:(id)item
108 contentRect:(NSRect*)contentRect {
109 return snapshotForShare_.get();
110 }
111
112 // Private methods
113
114 // Saves details required by delegate methods for the transition animation.
115 - (void)saveTransitionDataFromBrowser:(Browser*)browser {
116 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.
117
118 NSView* contentsView = [[windowForShare_ windowController] tabContentArea];
119 NSRect rectInWindow =
120 [[contentsView superview] convertRect:[contentsView frame] toView:nil];
121 rectForShare_ = [windowForShare_ convertRectToScreen:rectInWindow];
122
123 gfx::Image image;
124 gfx::Rect rect = gfx::Rect(NSRectToCGRect([contentsView bounds]));
125 if (ui::GrabViewSnapshot(contentsView, rect, &image)) {
126 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
127 }
128 }
129
130 - (void)clearTransitionData {
131 windowForShare_ = nil;
132 rectForShare_ = NSZeroRect;
133 snapshotForShare_.reset();
134 }
135
136 // Performs the share action using the sharing service represented by |sender|.
137 - (void)performShare:(NSMenuItem*)sender {
138 Browser* browser = chrome::GetLastActiveBrowser();
139 DCHECK(browser);
140 [self saveTransitionDataFromBrowser:browser];
141
142 content::WebContents* contents =
143 browser->tab_strip_model()->GetActiveWebContents();
144 NSURL* url = net::NSURLWithGURL(contents->GetLastCommittedURL());
145 NSString* title = base::SysUTF16ToNSString(contents->GetTitle());
146
147 NSSharingService* service =
148 base::mac::ObjCCastStrict<NSSharingService>([sender representedObject]);
149 [service setDelegate:self];
150 [service setSubject:title];
151
152 NSArray* itemsToShare;
153 if ([service
154 isEqual:[NSSharingService
155 sharingServiceNamed:NSSharingServiceNamePostOnTwitter]]) {
156 itemsToShare = @[ url, title ];
157 } else {
158 itemsToShare = @[ url ];
159 }
160 [service performWithItems:itemsToShare];
161 }
162
163 // Opens the "Sharing" subpane of the "Extensions" macOS preference pane.
164 - (void)openSharingPrefs:(NSMenuItem*)sender {
165 DCHECK([[self class] shouldShowMoreItem]);
166 NSURL* prefPaneURL =
167 [NSURL fileURLWithPath:kExtensionPrefPanePath isDirectory:YES];
168 NSDictionary* args = @{
169 kOpenSharingSubpaneActionKey : kOpenSharingSubpaneActionValue,
170 kOpenSharingSubpaneProtocolKey : kOpenSharingSubpaneProtocolValue
171 };
172 NSData* data = [NSPropertyListSerialization
173 dataWithPropertyList:args
174 format:NSPropertyListXMLFormat_v1_0
175 options:0
176 error:nil];
177 NSAppleEventDescriptor* descriptor = [[[NSAppleEventDescriptor alloc]
178 initWithDescriptorType:kOpenSharingSubpaneDescriptorType
179 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
180 [[NSWorkspace sharedWorkspace] openURLs:@[ prefPaneURL ]
181 withAppBundleIdentifier:nil
182 options:NSWorkspaceLaunchAsync
183 additionalEventParamDescriptor:descriptor
184 launchIdentifiers:NULL];
185 }
186
187 // Returns the image to be used for the "More..." menu item, or nil on macOS
188 // version where this private method is unsupported.
189 - (NSImage*)moreImage {
190 if ([NSSharingServicePicker
191 respondsToSelector:@selector(sharedMoreMenuImage)]) {
192 return
193 [NSSharingServicePicker performSelector:@selector(sharedMoreMenuImage)];
194 }
195 return nil;
196 }
197
198 // Creates a menu item that calls |service| when invoked.
199 - (NSMenuItem*)menuItemForService:(NSSharingService*)service {
200 BOOL isMail = [service
201 isEqual:[NSSharingService
202 sharingServiceNamed:NSSharingServiceNameComposeEmail]];
203 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.
204 NSString* title = isMail ? l10n_util::GetNSString(IDS_EMAIL_PAGE_LOCATION_MAC)
205 : service.menuItemTitle;
206 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.
207 action:@selector(performShare:)
208 keyEquivalent:keyEquivalent];
209 [item setTarget:self];
210 [item setImage:[service image]];
211 [item setRepresentedObject:service];
212 return [item autorelease];
213 }
214
215 @end
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698