1 /* -*- Mode: c++; tab-width: 2; indent-tabs-mode: nil; -*- */
2 /* This Source Code Form is subject to the terms of the Mozilla Public
3 * License, v. 2.0. If a copy of the MPL was not distributed with this
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 #import <Cocoa/Cocoa.h>
8 #include "nsMacSharingService.h"
11 #include "js/Array.h" // JS::NewArrayObject
12 #include "js/PropertyAndElement.h" // JS_SetElement, JS_SetProperty
13 #include "nsCocoaUtils.h"
14 #include "mozilla/MacStringHelpers.h"
16 NS_IMPL_ISUPPORTS(nsMacSharingService, nsIMacSharingService)
18 NSString* const remindersServiceName =
19 @"com.apple.reminders.RemindersShareExtension";
21 // These are some undocumented constants also used by Safari
22 // to let us open the preferences window
23 NSString* const extensionPrefPanePath =
24 @"/System/Library/PreferencePanes/Extensions.prefPane";
25 const UInt32 openSharingSubpaneDescriptorType = 'ptru';
26 NSString* const openSharingSubpaneActionKey = @"action";
27 NSString* const openSharingSubpaneActionValue = @"revealExtensionPoint";
28 NSString* const openSharingSubpaneProtocolKey = @"protocol";
29 NSString* const openSharingSubpaneProtocolValue = @"com.apple.share-services";
31 // Expose the id so we can pass reference through to JS and back
32 @interface NSSharingService (ExposeName)
36 // Filter providers that we do not want to expose to the user, because they are
37 // duplicates or do not work correctly within the context
38 static bool ShouldIgnoreProvider(NSString* aProviderName) {
40 isEqualToString:@"com.apple.share.System.add-to-safari-reading-list"];
43 // Clean up the activity once the share is complete
44 @interface SharingServiceDelegate : NSObject <NSSharingServiceDelegate> {
45 NSUserActivity* mShareActivity;
52 @implementation SharingServiceDelegate
54 - (id)initWithActivity:(NSUserActivity*)activity {
56 mShareActivity = [activity retain];
61 [mShareActivity resignCurrent];
62 [mShareActivity invalidate];
63 [mShareActivity release];
67 - (void)sharingService:(NSSharingService*)sharingService
68 didShareItems:(NSArray*)items {
72 - (void)sharingService:(NSSharingService*)service
73 didFailToShareItems:(NSArray*)items
74 error:(NSError*)error {
79 [mShareActivity release];
85 static NSString* NSImageToBase64(const NSImage* aImage) {
86 CGImageRef cgRef = [aImage CGImageForProposedRect:nil context:nil hints:nil];
87 NSBitmapImageRep* bitmapRep =
88 [[NSBitmapImageRep alloc] initWithCGImage:cgRef];
89 [bitmapRep setSize:[aImage size]];
91 [bitmapRep representationUsingType:NSBitmapImageFileTypePNG
93 NSString* base64Encoded = [imageData base64EncodedStringWithOptions:0];
95 return [NSString stringWithFormat:@"data:image/png;base64,%@", base64Encoded];
98 static void SetStrAttribute(JSContext* aCx, JS::Rooted<JSObject*>& aObj,
99 const char* aKey, NSString* aVal) {
101 mozilla::CopyNSStringToXPCOMString(aVal, strVal);
102 JS::Rooted<JSString*> title(aCx, JS_NewUCStringCopyZ(aCx, strVal.get()));
103 JS::Rooted<JS::Value> attVal(aCx, JS::StringValue(title));
104 JS_SetProperty(aCx, aObj, aKey, attVal);
107 nsresult nsMacSharingService::GetSharingProviders(
108 const nsAString& aPageUrl, JSContext* aCx,
109 JS::MutableHandle<JS::Value> aResult) {
110 NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
112 NSURL* url = nsCocoaUtils::ToNSURL(aPageUrl);
114 // aPageUrl is not a valid URL.
115 return NS_ERROR_FAILURE;
118 NSArray* sharingService = [NSSharingService sharingServicesForItems:@[ url ]];
119 int32_t serviceCount = 0;
120 JS::Rooted<JSObject*> array(aCx, JS::NewArrayObject(aCx, 0));
122 for (NSSharingService* currentService in sharingService) {
123 if (ShouldIgnoreProvider([currentService name])) {
126 JS::Rooted<JSObject*> obj(aCx, JS_NewPlainObject(aCx));
128 SetStrAttribute(aCx, obj, "name", [currentService name]);
129 SetStrAttribute(aCx, obj, "menuItemTitle", currentService.menuItemTitle);
130 SetStrAttribute(aCx, obj, "image", NSImageToBase64(currentService.image));
132 JS::Rooted<JS::Value> element(aCx, JS::ObjectValue(*obj));
133 JS_SetElement(aCx, array, serviceCount++, element);
136 aResult.setObject(*array);
139 NS_OBJC_END_TRY_BLOCK_RETURN(NS_ERROR_FAILURE);
143 nsMacSharingService::OpenSharingPreferences() {
144 NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
146 NSURL* prefPaneURL = [NSURL fileURLWithPath:extensionPrefPanePath
148 NSDictionary* args = @{
149 openSharingSubpaneActionKey : openSharingSubpaneActionValue,
150 openSharingSubpaneProtocolKey : openSharingSubpaneProtocolValue
152 NSData* data = [NSPropertyListSerialization
153 dataWithPropertyList:args
154 format:NSPropertyListXMLFormat_v1_0
157 NSAppleEventDescriptor* descriptor = [[NSAppleEventDescriptor alloc]
158 initWithDescriptorType:openSharingSubpaneDescriptorType
161 [[NSWorkspace sharedWorkspace] openURLs:@[ prefPaneURL ]
162 withAppBundleIdentifier:nil
163 options:NSWorkspaceLaunchAsync
164 additionalEventParamDescriptor:descriptor
165 launchIdentifiers:NULL];
167 [descriptor release];
170 NS_OBJC_END_TRY_BLOCK_RETURN(NS_ERROR_FAILURE);
174 nsMacSharingService::ShareUrl(const nsAString& aServiceName,
175 const nsAString& aPageUrl,
176 const nsAString& aPageTitle) {
177 NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
179 NSString* serviceName = nsCocoaUtils::ToNSString(aServiceName);
180 NSURL* pageUrl = nsCocoaUtils::ToNSURL(aPageUrl);
181 NSString* pageTitle = nsCocoaUtils::ToNSString(aPageTitle);
182 NSSharingService* service =
183 [NSSharingService sharingServiceNamed:serviceName];
185 // Reminders fetch its data from an activity, not the share data
186 if ([[service name] isEqual:remindersServiceName]) {
187 NSUserActivity* shareActivity = [[NSUserActivity alloc]
188 initWithActivityType:NSUserActivityTypeBrowsingWeb];
190 if ([pageUrl.scheme hasPrefix:@"http"]) {
191 [shareActivity setWebpageURL:pageUrl];
193 [shareActivity setEligibleForHandoff:NO];
194 [shareActivity setTitle:pageTitle];
195 [shareActivity becomeCurrent];
197 // Pass ownership of shareActivity to shareDelegate, which will release the
198 // activity once sharing has completed.
199 SharingServiceDelegate* shareDelegate =
200 [[SharingServiceDelegate alloc] initWithActivity:shareActivity];
201 [shareActivity release];
203 [service setDelegate:shareDelegate];
204 [shareDelegate release];
207 // Twitter likes the the title as an additional share item
208 NSArray* toShare = [[service name] isEqual:NSSharingServiceNamePostOnTwitter]
209 ? @[ pageUrl, pageTitle ]
212 [service setSubject:pageTitle];
213 [service performWithItems:toShare];
217 NS_OBJC_END_TRY_BLOCK_RETURN(NS_ERROR_FAILURE);