Bug 1363168 - Add support for OSX Share feature. r=Gijs,mstange
authorDale Harvey <dale@arandomurl.com>
Mon, 12 Mar 2018 09:16:51 +0000
changeset 413882 ced1f03536a4c5bf940a18139d25bd4f828251bb
parent 413881 066fefe52d2eb0d4315a232a14b2d10fb554ab2d
child 413883 212963ca6f2ba0108f411573deef62dc06746823
push id33853
push usercbrindusan@mozilla.com
push dateTue, 17 Apr 2018 09:51:13 +0000
treeherdermozilla-central@8b0ba3f7d099 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersGijs, mstange
bugs1363168
milestone61.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1363168 - Add support for OSX Share feature. r=Gijs,mstange MozReview-Commit-ID: sJXl2If9Ou
browser/base/content/browser-pageActions.js
browser/base/content/browser.xul
browser/base/content/test/urlbar/browser.ini
browser/base/content/test/urlbar/browser_page_action_menu_share_mac.js
browser/base/content/test/urlbar/browser_search_favicon.js
browser/locales/en-US/chrome/browser/browser.dtd
browser/modules/PageActions.jsm
browser/themes/osx/browser.css
browser/themes/osx/jar.mn
browser/themes/osx/share.svg
widget/cocoa/moz.build
widget/cocoa/nsMacSharingService.h
widget/cocoa/nsMacSharingService.mm
widget/cocoa/nsPrintDialogX.mm
widget/cocoa/nsWidgetFactory.mm
widget/moz.build
widget/nsIMacSharingService.idl
widget/nsWidgetsCID.h
widget/tests/unit/test_macsharingservice.js
widget/tests/unit/xpcshell.ini
--- a/browser/base/content/browser-pageActions.js
+++ b/browser/base/content/browser-pageActions.js
@@ -1253,8 +1253,65 @@ BrowserPageActions.addSearchEngine = {
         let prompt = Services.prompt.getPrompt(gBrowser.contentWindow, Ci.nsIPrompt);
         prompt.QueryInterface(Ci.nsIWritablePropertyBag2);
         prompt.setPropertyAsBool("allowTabModal", true);
         prompt.alert(title, text);
       },
     });
   },
 };
+
+// share URL
+BrowserPageActions.shareURL = {
+  onShowingInPanel(buttonNode) {
+    this._cached = false;
+  },
+
+  onPlacedInPanel(buttonNode) {
+    let action = PageActions.actionForID("shareURL");
+    BrowserPageActions.takeActionTitleFromPanel(action);
+  },
+
+  onShowingSubview(panelViewNode) {
+    // We cache the providers + the UI if the user selects the share
+    // panel multiple times while the panel is open.
+    if (this._cached) {
+      return;
+    }
+
+    let sharingService = this._sharingService;
+    let url = gBrowser.selectedBrowser.currentURI;
+    let currentURI = gURLBar.makeURIReadable(url).displaySpec;
+    let shareProviders = sharingService.getSharingProviders(currentURI);
+    let fragment = document.createDocumentFragment();
+
+    shareProviders.forEach(function(share) {
+      let item = document.createElement("toolbarbutton");
+      item.setAttribute("label", share.menuItemTitle);
+      item.setAttribute("share-title", share.title);
+      item.setAttribute("image", share.image);
+      item.classList.add("subviewbutton", "subviewbutton-iconic");
+
+      item.addEventListener("command", event => {
+        let shareTitle = event.target.getAttribute("share-title");
+        if (shareTitle) {
+          sharingService.shareUrl(shareTitle, currentURI);
+        }
+        PanelMultiView.hidePopup(BrowserPageActions.panelNode);
+      });
+
+      fragment.appendChild(item);
+    });
+
+    let bodyNode = panelViewNode.querySelector(".panel-subview-body");
+    while (bodyNode.firstChild) {
+      bodyNode.firstChild.remove();
+    }
+    bodyNode.appendChild(fragment);
+    this._cached = true;
+  }
+};
+
+// Attach sharingService here so tests can override the implementation
+XPCOMUtils.defineLazyServiceGetter(BrowserPageActions.shareURL,
+                                   "_sharingService",
+                                   "@mozilla.org/widget/macsharingservice;1",
+                                   "nsIMacSharingService");
--- a/browser/base/content/browser.xul
+++ b/browser/base/content/browser.xul
@@ -435,17 +435,18 @@
            flip="slide"
            photon="true"
            position="bottomcenter topright"
            tabspecific="true"
            noautofocus="true"
            copyURL-title="&pageAction.copyLink.label;"
            emailLink-title="&emailPageCmd.label;"
            sendToDevice-title="&pageAction.sendTabToDevice.label;"
-           sendToDevice-notReadyTitle="&sendToDevice.syncNotReady.label;">
+           sendToDevice-notReadyTitle="&sendToDevice.syncNotReady.label;"
+           shareURL-title="&pageAction.shareUrl.label;">
       <panelmultiview id="pageActionPanelMultiView"
                       mainViewId="pageActionPanelMainView"
                       viewCacheId="appMenu-viewCache">
         <panelview id="pageActionPanelMainView"
                    context="pageActionContextMenu"
                    class="PanelUI-subView">
           <vbox class="panel-subview-body"/>
         </panelview>
--- a/browser/base/content/test/urlbar/browser.ini
+++ b/browser/base/content/test/urlbar/browser.ini
@@ -47,16 +47,18 @@ support-files =
   page_action_menu_add_search_engine_one.html
   page_action_menu_add_search_engine_many.html
   page_action_menu_add_search_engine_same_names.html
   page_action_menu_add_search_engine_0.xml
   page_action_menu_add_search_engine_1.xml
   page_action_menu_add_search_engine_2.xml
 [browser_page_action_menu_clipboard.js]
 subsuite = clipboard
+[browser_page_action_menu_share_mac.js]
+skip-if = os != "mac" # Mac only feature
 [browser_pasteAndGo.js]
 subsuite = clipboard
 [browser_removeUnsafeProtocolsFromURLBarPaste.js]
 subsuite = clipboard
 [browser_search_favicon.js]
 [browser_tabMatchesInAwesomebar.js]
 support-files =
   moz.png
new file mode 100644
--- /dev/null
+++ b/browser/base/content/test/urlbar/browser_page_action_menu_share_mac.js
@@ -0,0 +1,72 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+/* global sinon */
+Services.scriptloader.loadSubScript("resource://testing-common/sinon-2.3.2.js");
+
+// Keep track of title of service we chose to share with
+let sharedTitle;
+let sharedUrl;
+
+let mockShareData = [{
+  title: "NSA",
+  menuItemTitle: "National Security Agency",
+  image: "data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEA" +
+    "LAAAAAABAAEAAAICTAEAOw=="
+}];
+
+let stub = sinon.stub(BrowserPageActions.shareURL, "_sharingService").get(() => {
+  return {
+    getSharingProviders(url) {
+      return mockShareData;
+    },
+    shareUrl(title, url) {
+      sharedUrl = url;
+      sharedTitle = title;
+    }
+  };
+});
+
+registerCleanupFunction(async function() {
+  stub.restore();
+  delete window.sinon;
+  await EventUtils.synthesizeNativeMouseMove(document.documentElement, 0, 0);
+  await PlacesUtils.history.clear();
+});
+
+add_task(async function shareURL() {
+  // Open an actionable page so that the main page action button appears.  (It
+  // does not appear on about:blank for example.)
+  let url = "http://example.org/";
+
+  await BrowserTestUtils.withNewTab(url, async () => {
+    // Open the panel.
+    await promisePageActionPanelOpen();
+
+    // Click Share URL.
+    let shareURLButton = document.getElementById("pageAction-panel-shareURL");
+    let viewPromise = promisePageActionViewShown();
+    EventUtils.synthesizeMouseAtCenter(shareURLButton, {});
+
+    let view = await viewPromise;
+    let body = document.getElementById(view.id + "-body");
+
+    Assert.equal(body.childNodes.length, 1, "Has correct share receivers");
+    let shareButton = body.childNodes[0];
+    Assert.equal(shareButton.label, mockShareData[0].menuItemTitle);
+    let hiddenPromise = promisePageActionPanelHidden();
+    // Click on share, panel should hide and sharingService should be
+    // given the title of service to share with
+    EventUtils.synthesizeMouseAtCenter(shareButton, {});
+    await hiddenPromise;
+
+    Assert.equal(sharedTitle, mockShareData[0].title,
+                 "Shared with the correct title");
+    Assert.equal(sharedUrl, "http://example.org/",
+                 "Shared correct URL");
+
+  });
+});
--- a/browser/base/content/test/urlbar/browser_search_favicon.js
+++ b/browser/base/content/test/urlbar/browser_search_favicon.js
@@ -6,16 +6,20 @@ registerCleanupFunction(() => {
   Services.prefs.clearUserPref(gRestyleSearchesPref);
   Services.search.currentEngine = gOriginalEngine;
   Services.search.removeEngine(gEngine);
   return PlacesUtils.history.clear();
 });
 
 add_task(async function() {
   Services.prefs.setBoolPref(gRestyleSearchesPref, true);
+
+  // This test is sensitive to the mouse position hovering awesome
+  // bar elements, so make sure it doesnt
+  await EventUtils.synthesizeNativeMouseMove(document.documentElement, 0, 0);
 });
 
 add_task(async function() {
 
   Services.search.addEngineWithDetails("SearchEngine", "", "", "",
                                        "GET", "http://s.example.com/search");
   gEngine = Services.search.getEngineByName("SearchEngine");
   gEngine.addParam("q", "{searchTerms}", null);
--- a/browser/locales/en-US/chrome/browser/browser.dtd
+++ b/browser/locales/en-US/chrome/browser/browser.dtd
@@ -989,16 +989,18 @@ you can use these alternative items. Oth
 <!ENTITY pageAction.removeFromUrlbar.label "Remove from Address Bar">
 <!ENTITY pageAction.allowInUrlbar.label "Show in Address Bar">
 <!ENTITY pageAction.disallowInUrlbar.label "Don’t Show in Address Bar">
 <!ENTITY pageAction.manageExtension.label "Manage Extension…">
 
 <!ENTITY pageAction.sendTabToDevice.label "Send Tab to Device">
 <!ENTITY sendToDevice.syncNotReady.label "Syncing Devices…">
 
+<!ENTITY pageAction.shareUrl.label "Share">
+
 <!ENTITY libraryButton.tooltip "View history, saved bookmarks, and more">
 
 <!-- LOCALIZATION NOTE: (accessibilityIndicator.tooltip): This is used to
      display a tooltip for accessibility indicator in toolbar/tabbar. It is also
      used as a textual label for the indicator used by assistive technology
      users. -->
 <!ENTITY accessibilityIndicator.tooltip "Accessibility Features Enabled">
 
--- a/browser/modules/PageActions.jsm
+++ b/browser/modules/PageActions.jsm
@@ -1162,16 +1162,35 @@ if (Services.prefs.getBoolPref("identity
     },
     onSubviewShowing(panelViewNode) {
       browserPageActions(panelViewNode).sendToDevice
         .onShowingSubview(panelViewNode);
     },
   });
 }
 
+if (AppConstants.platform == "macosx") {
+  gBuiltInActions.push(
+  // Share URL
+  {
+    id: "shareURL",
+    title: "shareURL-title",
+    onShowingInPanel(buttonNode) {
+      browserPageActions(buttonNode).shareURL.onShowingInPanel(buttonNode);
+    },
+    onPlacedInPanel(buttonNode) {
+      browserPageActions(buttonNode).shareURL.onPlacedInPanel(buttonNode);
+    },
+    wantsSubview: true,
+    onSubviewShowing(panelViewNode) {
+        browserPageActions(panelViewNode).shareURL
+          .onShowingSubview(panelViewNode);
+    },
+  });
+}
 
 /**
  * Gets a BrowserPageActions object in a browser window.
  *
  * @param  obj
  *         Either a DOM node or a browser window.
  * @return The BrowserPageActions object in the browser window related to the
  *         given object.
--- a/browser/themes/osx/browser.css
+++ b/browser/themes/osx/browser.css
@@ -330,16 +330,20 @@ html|input.urlbar-input {
 }
 
 .urlbar-display {
   margin-top: 0;
   margin-bottom: 0;
   color: GrayText;
 }
 
+#pageAction-panel-shareURL {
+  list-style-image: url("chrome://browser/skin/share.svg");
+}
+
 %include ../shared/urlbarSearchSuggestionsNotification.inc.css
 
 /* ----- AUTOCOMPLETE ----- */
 
 %include ../shared/autocomplete.inc.css
 %include ../shared/urlbar-autocomplete.inc.css
 
 :root {
--- a/browser/themes/osx/jar.mn
+++ b/browser/themes/osx/jar.mn
@@ -51,16 +51,17 @@ browser.jar:
   skin/classic/browser/places/livemark-item.png             (places/livemark-item.png)
   skin/classic/browser/preferences/alwaysAsk.png            (preferences/alwaysAsk.png)
   skin/classic/browser/preferences/application.png          (preferences/application.png)
   skin/classic/browser/preferences/saveFile.png             (preferences/saveFile.png)
 * skin/classic/browser/preferences/preferences.css          (preferences/preferences.css)
 * skin/classic/browser/preferences/in-content/preferences.css (preferences/in-content/preferences.css)
 * skin/classic/browser/preferences/in-content/dialog.css      (preferences/in-content/dialog.css)
   skin/classic/browser/preferences/applications.css         (preferences/applications.css)
+  skin/classic/browser/share.svg                            (share.svg)
   skin/classic/browser/tabbrowser/tabDragIndicator.png                   (tabbrowser/tabDragIndicator.png)
   skin/classic/browser/tabbrowser/tabDragIndicator@2x.png                (tabbrowser/tabDragIndicator@2x.png)
   skin/classic/browser/e10s-64@2x.png                                  (../shared/e10s-64@2x.png)
 
 [extensions/{972ce4c6-7e08-4474-a285-3208198ce6fd}] chrome.jar:
 % override chrome://browser/skin/feeds/audioFeedIcon.png                   chrome://browser/skin/feeds/feedIcon.png
 % override chrome://browser/skin/feeds/audioFeedIcon16.png                 chrome://browser/skin/feeds/feedIcon16.png
 % override chrome://browser/skin/feeds/videoFeedIcon.png                   chrome://browser/skin/feeds/feedIcon.png
new file mode 100644
--- /dev/null
+++ b/browser/themes/osx/share.svg
@@ -0,0 +1,7 @@
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+   - License, v. 2.0. If a copy of the MPL was not distributed with this
+   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16">
+  <path fill="context-fill" d="M12.707 4.294l-4-4A1 1 0 0 0 8.38.077a.984.984 0 0 0-.246-.05A.938.938 0 0 0 8 0a.938.938 0 0 0-.134.027.984.984 0 0 0-.246.05A1 1 0 0 0 7.291.3l-4 4a1 1 0 0 0 1.416 1.408L7 3.415V11a1 1 0 0 0 2 0V3.415l2.293 2.293a1 1 0 0 0 1.414-1.414z"></path>
+  <path fill="context-fill" d="M14 9a1 1 0 0 0-1 1v3a1 1 0 0 1-1 1H4a1 1 0 0 1-1-1v-3a1 1 0 0 0-2 0v3a3 3 0 0 0 3 3h8a3 3 0 0 0 3-3v-3a1 1 0 0 0-1-1z"></path>
+</svg>
--- a/widget/cocoa/moz.build
+++ b/widget/cocoa/moz.build
@@ -34,16 +34,17 @@ UNIFIED_SOURCES += [
     'nsColorPicker.mm',
     'nsCursorManager.mm',
     'nsDeviceContextSpecX.mm',
     'nsFilePicker.mm',
     'nsIdleServiceX.mm',
     'nsLookAndFeel.mm',
     'nsMacCursor.mm',
     'nsMacDockSupport.mm',
+    'nsMacSharingService.mm',
     'nsMacWebAppUtils.mm',
     'nsMenuBarX.mm',
     'nsMenuGroupOwnerX.mm',
     'nsMenuItemIconX.mm',
     'nsMenuItemX.mm',
     'nsMenuUtilsX.mm',
     'nsMenuX.mm',
     'nsPrintDialogX.mm',
new file mode 100644
--- /dev/null
+++ b/widget/cocoa/nsMacSharingService.h
@@ -0,0 +1,23 @@
+/* -*- Mode: c++; tab-width: 2; indent-tabs-mode: nil; -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsMacSharingService_h_
+#define nsMacSharingService_h_
+
+#include "nsIMacSharingService.h"
+
+class nsMacSharingService : public nsIMacSharingService
+{
+public:
+  nsMacSharingService() {}
+
+  NS_DECL_ISUPPORTS
+  NS_DECL_NSIMACSHARINGSERVICE
+
+protected:
+  virtual ~nsMacSharingService() {}
+};
+
+#endif // nsMacSharingService_h_
new file mode 100644
--- /dev/null
+++ b/widget/cocoa/nsMacSharingService.mm
@@ -0,0 +1,92 @@
+/* -*- Mode: c++; tab-width: 2; indent-tabs-mode: nil; -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#import <Cocoa/Cocoa.h>
+
+#include "nsMacSharingService.h"
+#include "nsCocoaUtils.h"
+#include "mozilla/MacStringHelpers.h"
+
+NS_IMPL_ISUPPORTS(nsMacSharingService, nsIMacSharingService)
+
+static NSString*
+NSImageToBase64(const NSImage* aImage)
+{
+  [aImage lockFocus];
+  NSBitmapImageRep* bitmapRep = [[NSBitmapImageRep alloc] initWithFocusedViewRect:NSMakeRect(0, 0, aImage.size.width, aImage.size.height)];
+  [aImage unlockFocus];
+  NSData* imageData = [bitmapRep representationUsingType:NSPNGFileType properties:@{}];
+  [bitmapRep release];
+  NSString* base64Encoded = [imageData base64EncodedStringWithOptions:0];
+  return [NSString stringWithFormat: @"data:image/png;base64,%@", base64Encoded];
+}
+
+static void
+SetStrAttribute(JSContext* aCx,
+                JS::Rooted<JSObject*>& aObj,
+                const char* aKey, NSString* aVal)
+{
+  nsAutoString strVal;
+  mozilla::CopyCocoaStringToXPCOMString(aVal, strVal);
+  JS::Rooted<JSString*> title(aCx, JS_NewUCStringCopyZ(aCx, strVal.get()));
+  JS::Rooted<JS::Value> attVal(aCx, JS::StringValue(title));
+  JS_SetProperty(aCx, aObj, aKey, attVal);
+}
+
+nsresult
+nsMacSharingService::GetSharingProviders(const nsAString& aUrlToShare,
+                                         JSContext* aCx,
+                                         JS::MutableHandleValue aResult)
+{
+  NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+  JS::Rooted<JSObject*> array(aCx, JS_NewArrayObject(aCx, 0));
+  NSURL* url = [NSURL URLWithString:nsCocoaUtils::ToNSString(aUrlToShare)];
+
+  NSArray* sharingService = [NSSharingService
+                             sharingServicesForItems:[NSArray arrayWithObject:url]];
+  int32_t serviceCount = 0;
+
+  for (NSSharingService *currentService in sharingService) {
+
+    JS::Rooted<JSObject*> obj(aCx, JS_NewPlainObject(aCx));
+
+    SetStrAttribute(aCx, obj, "title", currentService.title);
+    SetStrAttribute(aCx, obj, "menuItemTitle", currentService.menuItemTitle);
+    SetStrAttribute(aCx, obj, "image", NSImageToBase64(currentService.image));
+
+    JS::Rooted<JS::Value> element(aCx, JS::ObjectValue(*obj));
+    JS_SetElement(aCx, array, serviceCount++, element);
+  }
+
+  aResult.setObject(*array);
+
+  return NS_OK;
+  NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
+
+NS_IMETHODIMP
+nsMacSharingService::ShareUrl(const nsAString& aShareTitle,
+                              const nsAString& aUrlToShare)
+{
+  NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+  NSString* titleString = nsCocoaUtils::ToNSString(aShareTitle);
+  NSURL* url = [NSURL URLWithString:nsCocoaUtils::ToNSString(aUrlToShare)];
+
+  NSArray* sharingService = [NSSharingService
+                             sharingServicesForItems:[NSArray arrayWithObject:url]];
+
+  for (NSSharingService *currentService in sharingService) {
+    if ([currentService.title isEqualToString:titleString]) {
+      [currentService performWithItems:@[url]];
+      break;
+    }
+  }
+
+  return NS_OK;
+
+  NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
--- a/widget/cocoa/nsPrintDialogX.mm
+++ b/widget/cocoa/nsPrintDialogX.mm
@@ -1,14 +1,15 @@
 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "mozilla/ArrayUtils.h"
+#include "mozilla/gfx/PrintTargetCG.h"
 #include "mozilla/Preferences.h"
 
 #include "nsPrintDialogX.h"
 #include "nsIPrintSettings.h"
 #include "nsIPrintSettingsService.h"
 #include "nsPrintSettingsX.h"
 #include "nsCOMPtr.h"
 #include "nsQueryObject.h"
@@ -17,16 +18,17 @@
 #include "nsIStringBundle.h"
 #include "nsIWebBrowserPrint.h"
 #include "nsCRT.h"
 
 #import <Cocoa/Cocoa.h>
 #include "nsObjCExceptions.h"
 
 using namespace mozilla;
+using mozilla::gfx::PrintTarget;
 
 NS_IMPL_ISUPPORTS(nsPrintDialogServiceX, nsIPrintDialogService)
 
 nsPrintDialogServiceX::nsPrintDialogServiceX()
 {
 }
 
 nsPrintDialogServiceX::~nsPrintDialogServiceX()
--- a/widget/cocoa/nsWidgetFactory.mm
+++ b/widget/cocoa/nsWidgetFactory.mm
@@ -89,16 +89,19 @@ NS_GENERIC_FACTORY_CONSTRUCTOR(nsNativeM
 NS_GENERIC_FACTORY_CONSTRUCTOR(nsBidiKeyboard)
 
 #include "nsNativeThemeCocoa.h"
 NS_GENERIC_FACTORY_CONSTRUCTOR(nsNativeThemeCocoa)
 
 #include "nsMacDockSupport.h"
 NS_GENERIC_FACTORY_CONSTRUCTOR(nsMacDockSupport)
 
+#include "nsMacSharingService.h"
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsMacSharingService)
+
 #include "nsMacWebAppUtils.h"
 NS_GENERIC_FACTORY_CONSTRUCTOR(nsMacWebAppUtils)
 
 #include "nsStandaloneNativeMenu.h"
 NS_GENERIC_FACTORY_CONSTRUCTOR(nsStandaloneNativeMenu)
 
 #include "nsSystemStatusBarCocoa.h"
 NS_GENERIC_FACTORY_CONSTRUCTOR(nsSystemStatusBarCocoa)
@@ -129,16 +132,17 @@ NS_DEFINE_NAMED_CID(NS_SCREENMANAGER_CID
 NS_DEFINE_NAMED_CID(NS_DEVICE_CONTEXT_SPEC_CID);
 NS_DEFINE_NAMED_CID(NS_PRINTSESSION_CID);
 NS_DEFINE_NAMED_CID(NS_PRINTSETTINGSSERVICE_CID);
 NS_DEFINE_NAMED_CID(NS_PRINTDIALOGSERVICE_CID);
 NS_DEFINE_NAMED_CID(NS_IDLE_SERVICE_CID);
 NS_DEFINE_NAMED_CID(NS_SYSTEMALERTSSERVICE_CID);
 NS_DEFINE_NAMED_CID(NS_NATIVEMENUSERVICE_CID);
 NS_DEFINE_NAMED_CID(NS_MACDOCKSUPPORT_CID);
+NS_DEFINE_NAMED_CID(NS_MACSHARINGSERVICE_CID);
 NS_DEFINE_NAMED_CID(NS_MACWEBAPPUTILS_CID);
 NS_DEFINE_NAMED_CID(NS_STANDALONENATIVEMENU_CID);
 NS_DEFINE_NAMED_CID(NS_MACSYSTEMSTATUSBAR_CID);
 NS_DEFINE_NAMED_CID(NS_GFXINFO_CID);
 
 static const mozilla::Module::CIDEntry kWidgetCIDs[] = {
   { &kNS_WINDOW_CID, false, NULL, nsCocoaWindowConstructor },
   { &kNS_POPUP_CID, false, NULL, nsCocoaWindowConstructor },
@@ -165,16 +169,17 @@ static const mozilla::Module::CIDEntry k
   { &kNS_DEVICE_CONTEXT_SPEC_CID, false, NULL, nsDeviceContextSpecXConstructor },
   { &kNS_PRINTSESSION_CID, false, NULL, nsPrintSessionConstructor },
   { &kNS_PRINTSETTINGSSERVICE_CID, false, NULL, nsPrintSettingsServiceXConstructor },
   { &kNS_PRINTDIALOGSERVICE_CID, false, NULL, nsPrintDialogServiceXConstructor },
   { &kNS_IDLE_SERVICE_CID, false, NULL, nsIdleServiceXConstructor },
   { &kNS_SYSTEMALERTSSERVICE_CID, false, NULL, OSXNotificationCenterConstructor },
   { &kNS_NATIVEMENUSERVICE_CID, false, NULL, nsNativeMenuServiceXConstructor },
   { &kNS_MACDOCKSUPPORT_CID, false, NULL, nsMacDockSupportConstructor },
+  { &kNS_MACSHARINGSERVICE_CID, false, NULL, nsMacSharingServiceConstructor },
   { &kNS_MACWEBAPPUTILS_CID, false, NULL, nsMacWebAppUtilsConstructor },
   { &kNS_STANDALONENATIVEMENU_CID, false, NULL, nsStandaloneNativeMenuConstructor },
   { &kNS_MACSYSTEMSTATUSBAR_CID, false, NULL, nsSystemStatusBarCocoaConstructor },
   { &kNS_GFXINFO_CID, false, NULL, mozilla::widget::GfxInfoConstructor },
   { NULL }
 };
 
 static const mozilla::Module::ContractIDEntry kWidgetContracts[] = {
@@ -203,16 +208,17 @@ static const mozilla::Module::ContractID
   { "@mozilla.org/gfx/devicecontextspec;1", &kNS_DEVICE_CONTEXT_SPEC_CID },
   { "@mozilla.org/gfx/printsession;1", &kNS_PRINTSESSION_CID },
   { "@mozilla.org/gfx/printsettings-service;1", &kNS_PRINTSETTINGSSERVICE_CID },
   { NS_PRINTDIALOGSERVICE_CONTRACTID, &kNS_PRINTDIALOGSERVICE_CID },
   { "@mozilla.org/widget/idleservice;1", &kNS_IDLE_SERVICE_CID },
   { "@mozilla.org/system-alerts-service;1", &kNS_SYSTEMALERTSSERVICE_CID },
   { "@mozilla.org/widget/nativemenuservice;1", &kNS_NATIVEMENUSERVICE_CID },
   { "@mozilla.org/widget/macdocksupport;1", &kNS_MACDOCKSUPPORT_CID },
+  { "@mozilla.org/widget/macsharingservice;1", &kNS_MACSHARINGSERVICE_CID },
   { "@mozilla.org/widget/mac-web-app-utils;1", &kNS_MACWEBAPPUTILS_CID },
   { "@mozilla.org/widget/standalonenativemenu;1", &kNS_STANDALONENATIVEMENU_CID },
   { "@mozilla.org/widget/macsystemstatusbar;1", &kNS_MACSYSTEMSTATUSBAR_CID },
   { "@mozilla.org/gfx/info;1", &kNS_GFXINFO_CID },
   { NULL }
 };
 
 static void
--- a/widget/moz.build
+++ b/widget/moz.build
@@ -54,16 +54,17 @@ if toolkit == 'windows':
         'nsITaskbarTabPreview.idl',
         'nsITaskbarWindowPreview.idl',
         'nsIWindowsUIUtils.idl',
         'nsIWinTaskbar.idl',
     ]
 elif toolkit == 'cocoa':
     XPIDL_SOURCES += [
         'nsIMacDockSupport.idl',
+        'nsIMacSharingService.idl',
         'nsIMacWebAppUtils.idl',
         'nsIStandaloneNativeMenu.idl',
         'nsISystemStatusBar.idl',
         'nsITaskbarProgress.idl',
     ]
     EXPORTS += [
         'nsINativeMenuService.h',
     ]
new file mode 100644
--- /dev/null
+++ b/widget/nsIMacSharingService.idl
@@ -0,0 +1,23 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+/**
+ * Allow applications to interface with the Mac OS X Sharing APIs.
+ */
+
+[scriptable, uuid(de59fe1a-46c8-490f-b04d-34545acb06c9)]
+interface nsIMacSharingService : nsISupports
+{
+  /**
+   * Get list of sharing providers
+   */
+  [implicit_jscontext] jsval getSharingProviders(in AString urlToShare);
+
+  /**
+   * Launch service with shareTitle with given url
+   */
+  void shareUrl(in AString shareTitle, in AString urlToShare);
+};
--- a/widget/nsWidgetsCID.h
+++ b/widget/nsWidgetsCID.h
@@ -70,16 +70,21 @@
 { 0x1F39AE50, 0xB6A0, 0x4B37,                         \
   { 0x90, 0xF4, 0x60, 0xAF, 0x61, 0x41, 0x93, 0xD8 }}
 
 // {2451BAED-8DC3-46D9-9E30-96E1BAA03666}
 #define NS_MACDOCKSUPPORT_CID \
 { 0x2451BAED, 0x8DC3, 0x46D9, \
   { 0x9E, 0x30, 0x96, 0xE1, 0xBA, 0xA0, 0x36, 0x66 } }
 
+// {de59fe1a-46c8-490f-b04d-34545acb06c9}
+#define NS_MACSHARINGSERVICE_CID \
+{ 0xde59fe1a, 0x46c8, 0x490f, \
+  { 0xb0, 0x4d, 0x34, 0x54, 0x5a, 0xcb, 0x06, 0xc9 } }
+
 // {b6e1a890-b2b8-4883-a65f-9476f6185313}
 #define NS_MACSYSTEMSTATUSBAR_CID \
 { 0xb6e1a890, 0xb2b8, 0x4883, \
   { 0xa6, 0x5f, 0x94, 0x76, 0xf6, 0x18, 0x53, 0x13 } }
 
 //-----------------------------------------------------------
 //Drag & Drop & Clipboard
 //-----------------------------------------------------------
new file mode 100644
--- /dev/null
+++ b/widget/tests/unit/test_macsharingservice.js
@@ -0,0 +1,27 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+//Basic tests to verify that MacSharingService returns expected data
+
+ChromeUtils.import("resource://gre/modules/Services.jsm");
+ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
+
+function test_getSharingProviders()
+{
+  let sharingService = Cc["@mozilla.org/widget/macsharingservice;1"].
+      getService(Ci.nsIMacSharingService);
+  let providers = sharingService.getSharingProviders("http://example.org");
+  Assert.ok(providers.length > 1, "There are providers returned");
+  providers.forEach(provider => {
+    Assert.ok("title" in provider, "Provider has title");
+    Assert.ok("menuItemTitle" in provider, "Provider has menuItemTitle");
+    Assert.ok("image" in provider, "Provider has image");
+  });
+}
+
+function run_test()
+{
+  test_getSharingProviders();
+}
--- a/widget/tests/unit/xpcshell.ini
+++ b/widget/tests/unit/xpcshell.ini
@@ -1,6 +1,8 @@
 [DEFAULT]
 head = 
 
 [test_taskbar_jumplistitems.js]
+[test_macsharingservice.js]
+skip-if = os != "mac"
 [test_macwebapputils.js]
 skip-if = os != "mac"